Repository: Fumo-Unlockers/Xbox-Achievement-Unlocker Branch: Main Commit: 1608ac50faa7 Files: 148 Total size: 590.9 KB Directory structure: gitextract_1b3dwlvx/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── feature_request.yml │ │ └── game_request.yml │ └── workflows/ │ ├── Build-Events-Zip.yml │ └── Build-Pre-Release.yml ├── .gitignore ├── .gitmodules ├── .idea/ │ └── .idea.XAU/ │ └── .idea/ │ ├── .gitignore │ ├── encodings.xml │ ├── indexLayout.xml │ ├── riderPublish.xml │ └── vcs.xml ├── .run/ │ ├── Debug.run.xml │ ├── Publish Debug.run.xml │ └── Publish Release.run.xml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Clown-Behaviour.md ├── Doc/ │ └── Events.md ├── LICENSE ├── LICENSE.MIT ├── README.md ├── XAU/ │ ├── App.xaml │ ├── App.xaml.cs │ ├── AssemblyInfo.cs │ ├── Models/ │ │ ├── GitHubResponse.cs │ │ ├── MiscItems.cs │ │ ├── XAUSettings.cs │ │ ├── XboxApiRequest.cs │ │ └── XboxApiResponse.cs │ ├── Networking/ │ │ ├── DBoxRestAPI.cs │ │ ├── GithubRestApi.cs │ │ └── XboxRestApi.cs │ ├── Services/ │ │ ├── ApplicationHostService.cs │ │ └── HttpServer/ │ │ ├── AchievementRoutes.cs │ │ ├── EndpointRoutes.cs │ │ ├── GameRoutes.cs │ │ ├── HttpServer.cs │ │ ├── ProfileRoutes.cs │ │ ├── Routes.cs │ │ └── SpoofingRoutes.cs │ ├── Theme/ │ │ └── ThemeConstants.xaml │ ├── Usings.cs │ ├── Util/ │ │ ├── Constants/ │ │ │ └── Constants.cs │ │ ├── Etw/ │ │ │ └── EtwTokenCapture.cs │ │ ├── Files/ │ │ │ └── FileDownloader.cs │ │ ├── Memory/ │ │ │ ├── Methods/ │ │ │ │ ├── AoB.cs │ │ │ │ └── Read.cs │ │ │ ├── Structures/ │ │ │ │ ├── Imports.cs │ │ │ │ ├── MemoryRegionResult.cs │ │ │ │ └── Process.cs │ │ │ └── memory.cs │ │ └── XboxAuthNet/ │ │ ├── Base64UrlHelper.cs │ │ ├── ErrorCodes.cs │ │ ├── ErrorHelper.cs │ │ ├── FodyWeavers.xml │ │ ├── HttpHelper.cs │ │ ├── Jwt/ │ │ │ └── JwtDecoder.cs │ │ ├── OAuth/ │ │ │ ├── CodeFlow/ │ │ │ │ ├── AuthCodeException.cs │ │ │ │ ├── CodeFlowAuthenticator.cs │ │ │ │ ├── CodeFlowAuthorizationResult.cs │ │ │ │ ├── CodeFlowBuilder.cs │ │ │ │ ├── CodeFlowLiveApiClient.cs │ │ │ │ ├── CodeFlowUriChecker.cs │ │ │ │ ├── ICodeFlowApiClient.cs │ │ │ │ ├── ICodeFlowUrlChecker.cs │ │ │ │ ├── IWebUI.cs │ │ │ │ ├── MicrosoftOAuthPromptModes.cs │ │ │ │ ├── Parameters/ │ │ │ │ │ ├── CodeFlowAccessTokenParameter.cs │ │ │ │ │ ├── CodeFlowAuthorizationParameter.cs │ │ │ │ │ ├── CodeFlowParameter.cs │ │ │ │ │ └── CodeFlowRefreshTokenParameter.cs │ │ │ │ └── WebUIOptions.cs │ │ │ ├── MicrosoftOAuthException.cs │ │ │ ├── MicrosoftOAuthResponse.cs │ │ │ └── MicrosoftUserPayload.cs │ │ ├── PlatformManager.cs │ │ ├── Platforms/ │ │ │ └── WinForm/ │ │ │ ├── StaTaskScheduler.cs │ │ │ ├── UIThreadHelper.cs │ │ │ ├── WebView2WebUI.cs │ │ │ ├── Win32Window.cs │ │ │ ├── WinFormsPanelWithWebView2.cs │ │ │ └── WindowsDpiHelper.cs │ │ ├── XboxAuthNet.csproj │ │ └── XboxLive/ │ │ ├── Crypto/ │ │ │ ├── ECDCertificatePopCryptoProvider.cs │ │ │ ├── IPopCryptoProvider.cs │ │ │ ├── IXboxRequestSigner.cs │ │ │ └── XboxRequestSigner.cs │ │ ├── Requests/ │ │ │ ├── AbstractXboxAuthRequest.cs │ │ │ ├── AbstractXboxSignedAuthRequest.cs │ │ │ ├── CommonRequestHeaders.cs │ │ │ ├── XboxDeviceTokenRequest.cs │ │ │ ├── XboxSignedUserTokenRequest.cs │ │ │ ├── XboxSignedXstsRequest.cs │ │ │ ├── XboxSisuAuthRequest.cs │ │ │ ├── XboxTitleTokenRequest.cs │ │ │ ├── XboxUserTokenRequest.cs │ │ │ └── XboxXstsRequest.cs │ │ ├── Responses/ │ │ │ ├── XErrJsonConverter.cs │ │ │ ├── XboxAuthResponse.cs │ │ │ ├── XboxAuthResponseHandler.cs │ │ │ ├── XboxAuthXuiClaims.cs │ │ │ ├── XboxAuthXuiClaimsJsonConverter.cs │ │ │ ├── XboxErrorResponse.cs │ │ │ └── XboxSisuResponse.cs │ │ ├── XboxAuthClient.cs │ │ ├── XboxAuthConstants.cs │ │ ├── XboxAuthException.cs │ │ ├── XboxAuthXuiClaimNames.cs │ │ ├── XboxDeviceTypes.cs │ │ ├── XboxGameTitles.cs │ │ └── XboxSignedClient.cs │ ├── ViewModels/ │ │ ├── Pages/ │ │ │ ├── AchievementsViewModel.cs │ │ │ ├── DebugViewModel.cs │ │ │ ├── GamesViewModel.cs │ │ │ ├── HomeViewModel.cs │ │ │ ├── InfoViewModel.cs │ │ │ ├── MiscViewModel.cs │ │ │ ├── SettingsViewModel.cs │ │ │ └── StatsViewModel.cs │ │ └── Windows/ │ │ └── MainWindowViewModel.cs │ ├── Views/ │ │ ├── Pages/ │ │ │ ├── AchievementsPage.xaml │ │ │ ├── AchievementsPage.xaml.cs │ │ │ ├── DebugPage.xaml │ │ │ ├── DebugPage.xaml.cs │ │ │ ├── GamesPage.xaml │ │ │ ├── GamesPage.xaml.cs │ │ │ ├── HomePage.xaml │ │ │ ├── HomePage.xaml.cs │ │ │ ├── InfoPage.xaml │ │ │ ├── InfoPage.xaml.cs │ │ │ ├── MiscPage.xaml │ │ │ ├── MiscPage.xaml.cs │ │ │ ├── PlaceholderPage.xaml │ │ │ ├── PlaceholderPage.xaml.cs │ │ │ ├── SettingsPage.xaml │ │ │ ├── SettingsPage.xaml.cs │ │ │ ├── StatsPage.xaml │ │ │ └── StatsPage.xaml.cs │ │ └── Windows/ │ │ ├── MainWindow.xaml │ │ └── MainWindow.xaml.cs │ ├── XAU.csproj │ └── app.manifest └── XAU.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # Remove the line below if you want to inherit .editorconfig settings from higher directories root = true # C# files [*.cs] #### Core EditorConfig Options #### # Indentation and spacing indent_size = 4 indent_style = space tab_width = 4 insert_final_newline = true trim_trailing_whitespace = true # charset charset = utf-8 end_of_line = lf #### .NET Coding Conventions #### # this. and Me. preferences dotnet_style_qualification_for_method = true #### Diagnostic configuration #### # CA1000: Do not declare static members on generic types dotnet_diagnostic.CA1000.severity = warning ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .github/FUNDING.yml ================================================ github: ItsLogic ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug Report description: File a bug report title: "[Bug]: " labels: ["bug"] body: - type: markdown attributes: value: | Make sure you fill in every section correctly and with as much detail as possible. - type: input id: XAU-Ver attributes: label: What version of the Xbox Achievement Unlocker were you using (you can see this in the info page)? placeholder: 2.x validations: required: true - type: dropdown id: Tool-Section attributes: label: Tool Section description: What area of the tool did the issue occur? options: - Home - Games - Achievements - Misc- Spoofer - Misc- Search - Settings - Other- Please put more information in the box below validations: required: true - type: textarea id: issue-description attributes: label: What is the bug? 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) validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: Feature/Improvement request description: Make a request for a new feature or an improvement to an existing feature title: "[Enhancement]: " labels: ["enhancement"] body: - type: markdown attributes: value: | Make sure you fill in every section correctly and with as much detail as possible. - type: dropdown id: RequestSelection attributes: label: Request Selection description: Are you asking for an improvement or a new feature? options: - New Feature - Improvement validations: required: true - type: textarea id: RequestDescription attributes: label: What is your request? description: What do you want? Where do you want it? What do you want it to do? validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/game_request.yml ================================================ name: Game Request description: Request support for an event-based game title: "[Game]: " labels: ["Game"] body: - type: markdown attributes: value: "Please fill in each section accurately." - type: checkboxes id: duplicate attributes: label: Confirmation description: Confirm before submitting options: - label: I have verified that this is not a duplicate request. required: true - type: input id: GameLink attributes: label: Game Link description: Provide a True Achievements or Xbox store link for the game you want supported. placeholder: e.g., https://www.trueachievements.com/game/Forza-Horizon-3/achievements validations: required: true - type: dropdown id: Tool-Section attributes: label: Game Platform description: Specify the platforms on which the Xbox version of this game is available. options: - Console only - Console and PC - PC only validations: required: true - type: textarea id: issue-description attributes: label: Additional Game Information description: Provide any additional information about the game or specific achievements you want supported. validations: required: true ================================================ FILE: .github/workflows/Build-Events-Zip.yml ================================================ name: Update Events Data on: push: paths: - 'Events/**' - 'Events' branches: - Main jobs: zip-and-push: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 with: submodules: recursive fetch-depth: 0 - name: Ensure submodules are initialized and at the recorded commit run: | # sync submodule URLs, init and update to the recorded gitlinks git submodule sync --recursive git submodule update --init --recursive --force - name: Install zip run: sudo apt-get install zip - name: Zip Events folder run: zip -r Events.zip Events - name: Create meta.json run: | echo '{ "Timestamp": '$(date +%s)', "DataVersion": "1.0" }' > meta.json - name: Setup Git run: | git config --global user.email "38233332+ItsLogic@users.noreply.github.com" git config --global user.name "Events Data Action" - name: Create and switch to new branch run: | git checkout --orphan Events-Data git rm --cached -r . - name: Add files to new branch run: | git add Events.zip meta.json git commit -m "Update events data: $(date -u +'%Y-%m-%d %H:%M:%S')" - name: Force Push to new branch run: git push --force origin Events-Data ================================================ FILE: .github/workflows/Build-Pre-Release.yml ================================================ name: .NET 8 Build on: push: branches: [ Main ] paths-ignore: - '**.md' - '**/ISSUE_TEMPLATE/**' - '**.json' - '.gitignore' - '.gitattributes' jobs: build: runs-on: windows-latest steps: - uses: actions/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v1 with: dotnet-version: '9.0.x' - name: Get latest commit hash id: get-hash run: echo "hash=$(git rev-parse HEAD)" >> $env:GITHUB_OUTPUT - name: Replace ToolVersion with commit hash run: | $content = Get-Content -Path "${{github.workspace}}/XAU/ViewModels/Pages/HomeViewModel.cs" -Raw $content = $content -replace 'public static string ToolVersion = "EmptyDevToolVersion";', 'public static string ToolVersion = "DEV-${{ steps.get-hash.outputs.hash }}";' $content = $content -replace 'Tool Version: {System.Reflection.Assembly.GetExecutingAssembly().GetName().Version}', 'Tool Version: DEV-${{ steps.get-hash.outputs.hash }}' Set-Content -Path "${{github.workspace}}/XAU/ViewModels/Pages/HomeViewModel.cs" -Value $content - name: Restore dependencies run: dotnet restore /p:EnableWindowsTargeting=true - name: Build 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 - name: Copy .exe file to temp run: | New-Item -ItemType Directory -Force -Path "${{github.workspace}}/../temp" Copy-Item -Path "./publish/XAU.exe" -Destination "${{github.workspace}}/../temp/" - name: Copy .exe file to workspace run: | Remove-Item -Path "${{github.workspace}}/XAU.exe" -ErrorAction Ignore Move-Item -Path "${{github.workspace}}/../temp/XAU.exe" -Destination "${{github.workspace}}/XAU.exe" - name: Write download URL and version to JSON file run: | echo "{ `"DownloadURL`": `"https://github.com/${{ github.repository }}/raw/Pre-Release/XAU.exe`", `"LatestBuildVersion`": `"${{ steps.get-hash.outputs.hash }}`" }" | Out-File -FilePath info.json - name: Create and switch to new branch run: | git checkout --orphan Pre-Release git rm -f --cached -r . - name: Setup Git run: | git config --global user.email "38233332+ItsLogic@users.noreply.github.com" git config --global user.name "Pre Release Action" - name: Add files to new branch run: | git add XAU.exe info.json git commit -m "Update pre-release version: $(date -u +'%Y-%m-%d %H:%M:%S')" - name: Force Push to new branch run: git push --force origin Pre-Release ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Oo]ut/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # ASP.NET Scaffolding ScaffoldingReadMe.txt # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool coverage*.json coverage*.xml coverage*.info # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ # Fody - auto-generated XML schema FodyWeavers.xsd ================================================ FILE: .gitmodules ================================================ [submodule "Events"] path = Events url = https://github.com/Fumo-Unlockers/XAU-Events ================================================ FILE: .idea/.idea.XAU/.idea/.gitignore ================================================ # Default ignored files /shelf/ /workspace.xml # Rider ignored files /projectSettingsUpdater.xml /.idea.XAU.iml /modules.xml /contentModel.xml # Editor-based HTTP Client requests /httpRequests/ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml ================================================ FILE: .idea/.idea.XAU/.idea/encodings.xml ================================================ ================================================ FILE: .idea/.idea.XAU/.idea/indexLayout.xml ================================================ ================================================ FILE: .idea/.idea.XAU/.idea/riderPublish.xml ================================================ ================================================ FILE: .idea/.idea.XAU/.idea/vcs.xml ================================================ ================================================ FILE: .run/Debug.run.xml ================================================  ================================================ FILE: .run/Publish Debug.run.xml ================================================  ================================================ FILE: .run/Publish Release.run.xml ================================================  ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct ## 1. Acceptance We 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. ## 2. No Hate We strictly prohibit any form of hate speech, discrimination, or prejudice. We encourage open-mindedness and respect for all members of our community. ## 3. Respect We expect all members of our community to treat each other with respect and kindness. Disagreements should be handled with maturity and understanding. ## 4. Violations Violations of this code of conduct will not be tolerated and may result in expulsion from the community. By participating in this community, you agree to abide by this code of conduct. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Guidelines We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: - Reporting a bug - Discussing the current state of the code - Submitting a fix - Proposing new features ## We Develop with Github We use github to host code, to track issues and feature requests, as well as accept pull requests. Pull Requests are the best way to propose changes to the codebase. We actively welcome your pull requests if: 1. They are functional 2. They improve the tool and/or repository 3. They are easy to understand and check ## Any contributions you make will be under the GNU GPL License In 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. ## Report bugs using Github's issues We 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! ## Write bug reports with detail and background Great Bug Reports tend to have: - A quick summary and/or background - Steps to reproduce - Be specific! - What you expected would happen - What actually happens ================================================ FILE: Clown-Behaviour.md ================================================ # This a fun list of people acting like clowns ## 1. S1MXN (Simon) This user goes by S1MXN on [youtube](https://www.youtube.com/@S1MXN) and s1mxnyt (285913424716627970) on discord. He 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. In this example he even claims to want to take down other peoples videos for copying his work: ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/c051cb5a-2244-4c6b-8c86-d9e509d80da1) Here 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) ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/dca8ed48-7d6b-4514-8ec4-4d9505404af4) ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/3a633cc9-3b7c-43a5-b500-04a016e17708) He 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. I first pretty innocently ask about the tool as a concerned user as it has stopped working ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/4b5566f6-f773-43fe-8f52-67aa02737552) I then link this github repo and ask if the code was stolen from him ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/5004e06e-9556-42f7-b90a-1329684161db) He responds to my surprise with yes ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/3994e933-ee00-4970-8e30-e7a95be18e51) I 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 ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/7383c987-873a-4c67-bda1-7a03490a9395) ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/7280b2ee-ef78-42ce-a204-bf2fcedf58e3) --- ## 2. relights This user goes by relights/em on discord (761088676675321856) This user first linked the S1MXN video claiming it was the "only legit tool" ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/dbee43ad-0853-460b-b7db-6d06566a6e6b) They then claim to have made the tool themselves ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/378edb83-648b-48b2-b92b-000afe78a39e) When called out they double down ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/48770120-7d0b-4f23-b2e4-8c75139e99b3) I then replied to this message with the file hashes proving they did not edit anything and its just a direct rip ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/a33e5ed1-1ef3-4678-9cb4-2d9f9f6c8e08) The interaction ends with them repeating the same stuff ![image](https://github.com/ItsLogic/Xbox-Achievement-Unlocker/assets/38233332/f712b07c-c547-445a-b31e-c0ffb5d8bf62) ================================================ FILE: Doc/Events.md ================================================ # Events Guide This guide provides information on how to work with events, set up your system to see events, and how to use events in the tool. ## Video Guide These guides will serve as video guides to getting the prerequisites setup. Please reference the text guide below for details. ### asutermo/Math/Shodan/etc's Guide [![asutermo/Math/Shodan/etc's Guide](https://img.youtube.com/vi/1UrEZi2p6w4/hqdefault.jpg)](https://youtu.be/1UrEZi2p6w4) ### Cracked Fisk's Guide [![Cracked Fisk's Guide](https://img.youtube.com/vi/9-oAuq6zFVg/hqdefault.jpg)](https://www.youtube.com/watch?v=9-oAuq6zFVg) ## Table of Contents - [Events Guide](#events-guide) - [Video Guide](#video-guide) - [asutermo/Math/Shodan/etc's Guide](#asutermomathshodanetcs-guide) - [Cracked Fisk's Guide](#cracked-fisks-guide) - [Table of Contents](#table-of-contents) - [Information](#information) - [Events Overview](#events-overview) - [Anonymising Events](#anonymising-events) - [Raw Event](#raw-event) - [Anonymised Event](#anonymised-event) - [Setup Guide](#setup-guide) - [Prerequisites](#prerequisites) - [Frida](#frida) - [Wireshark](#wireshark) - [Windows Settings](#windows-settings) - [Viewing Events](#viewing-events) - [Sending Events](#sending-events) - [Headers](#headers) - [Body](#body) - [Errors](#errors) - [Using Events In The Tool](#using-events-in-the-tool) - [Support Overview](#support-overview) - [Obtaining Your Events Token](#obtaining-your-events-token) - [Adding Your Own Game](#adding-your-own-game) - [Good Luck Achievements](#good-luck-achievements) - [Bad Luck Achievements](#bad-luck-achievements) - [Preparing a Template](#preparing-a-template) - [Preparing the Criteria](#preparing-the-criteria) - [Standard Replace](#standard-replace) - [Range Replace (Int)](#range-replace-int) - [Range Replace (Float)](#range-replace-float) - [Special Replacements](#special-replacements) - [Ordering Criteria](#ordering-criteria) - [Examples](#examples) - [Good Luck Achievement](#good-luck-achievement) - [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) - [Step 2. Figure out the important values within the data section](#step-2-figure-out-the-important-values-within-the-data-section) - [Step 3. Create the template](#step-3-create-the-template) - [Step 4. Create the data](#step-4-create-the-data) - [Step 5. Add the achievement criteria](#step-5-add-the-achievement-criteria) - [Bad Luck Achievement](#bad-luck-achievement) - [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) - [Step 2. Figure out the important values within the data section](#step-2-figure-out-the-important-values-within-the-data-section-1) - [Step 3. Create the template](#step-3-create-the-template-1) - [Step 4. Create the data](#step-4-create-the-data-1) - [Step 5. Add the achievement criteria](#step-5-add-the-achievement-criteria-1) - [Stats Editor](#stats-editor) ## Information > [!WARNING] > This document is techincal and is not intended to be user friendly. This is not intended for normal users. ### Events Overview Each 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. #### Anonymising Events Events 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) ##### Raw Event ```json { "ver": "4.0", "name": "Microsoft.XboxLive.T1289871275.CarAddedToGarage", "time": "2024-04-07T01:11:53.6748551Z", "iKey": "o:0890af88a9ed4cc886a14f5e174a2827", "ext": { "utc": { "shellId": 281572416699236352, "eventFlags": 514, "pgName": "XBOX", "flags": 880804513, "epoch": "903317", "seq": 807 }, "privacy": { "isRequired": false }, "metadata": { "f": { "baseData": { "f": { "properties": { "f": { "BuildNum": 4, "CarId": 4, "IsHorizonEdition": 4, "NumCarsInGarage": 4, "Track": 4 } } } } }, "policies": 0 }, "os": { "bootId": 284, "name": "Windows", "ver": "10.0.22621.3296.amd64fre.ni_release.220506-1250", "expId": "RS:1B54D,MD:283BAEF,ME:28279A6,MD:2A69053,MD:255521A" }, "app": { "id": "U:Microsoft.OpusPG_1.0.125.2_x64__8wekyb3d8bbwe!OpusReleaseFinal", "ver": "1.0.125.2_x64_!2018/06/05:22:52:33!60EF05C!forza_x64_release_final.exe", "is1P": 1, "asId": 898 }, "device": { "localId": "s:0039DA16-8F86-463F-9EBE-CB0619831735", "deviceClass": "Windows.Desktop" }, "protocol": { "devMake": "Micro-Star International Co., Ltd.", "devModel": "MS-7E06", "ticketKeys": ["21171495"] }, "user": { "localId": "w:EFEB6F6A-3B57-C3DA-5A6B-B5908C25B28C" }, "loc": { "tz": "+01:00" } }, "data": { "baseType": "Microsoft.XboxLive.InGame", "baseData": { "name": "CarAddedToGarage", "serviceConfigId": "19020100-9575-4c2b-9916-3d664ce1dfab", "playerSessionId": "DBF9D623-02F2-4CAB-866E-EDF6908C9491", "titleId": "1289871275", "userId": "1234567890123456", "ver": 1, "properties": { "BuildNum": 156858, "CarId": 302, "IsHorizonEdition": 0, "NumCarsInGarage": 20, "Track": 552 } } } } ``` ##### Anonymised Event ```json { "ver": "4.0", "name": "Microsoft.XboxLive.T1289871275.CarAddedToGarage", "time": "REPLACETIME", "iKey": "o:0890af88a9ed4cc886a14f5e174a2827", "ext": { "utc": { "shellId": 1, "eventFlags": 1, "pgName": "XBOX", "flags": 1, "epoch": "1", "seq": REPLACESEQ }, "privacy": { "isRequired": false }, "metadata": { "f": { "baseData": { "f": { "properties": { "f": { "BuildNum": 1, "CarId": 1, "IsHorizonEdition": 1, "NumCarsInGarage": 1, "Track": 1 } } } } }, "policies": 0 }, "os": { "bootId": 1, "name": "1", "ver": "1", "expId": "1" }, "app": { "id": "U:Microsoft.OpusPG_1.0.125.2_x64__8wekyb3d8bbwe!OpusReleaseFinal", "ver": "1.0.125.2_x64_!2018/06/05:22:52:33!60EF05C!forza_x64_release_final.exe", "is1P": 1, "asId": 1 }, "device": { "localId": "s:11111111-1111-1111-1111-111111111111", "deviceClass": "Windows.Desktop" }, "protocol": { "devMake": "1", "devModel": "1", "ticketKeys": [ "1" ] }, "user": { "localId": "w:11111111-1111-1111-1111-111111111111" }, "loc": { "tz": "+01:00" } }, "data": { "baseType": "Microsoft.XboxLive.InGame", "baseData": { "name": "CarAddedToGarage", "serviceConfigId": "19020100-9575-4c2b-9916-3d664ce1dfab", "playerSessionId": "11111111-1111-1111-1111-111111111111", "titleId": "1289871275", "userId": "REPLACEXUID", "ver": 1, "properties": { "BuildNum": 156858, "CarId": 302, "IsHorizonEdition": 0, "NumCarsInGarage": 20, "Track": 552 } } } } ``` ### Setup Guide This section guides you through the process of setting up your system to view events. #### Prerequisites To monitor events and use the event-based unlocker in the tool, you need to install Wireshark and Frida and configure them. ##### Frida The easiest way to install frida is using python pip. You can install frida by running the command ``` pip install frida-tools ``` You can view all install methods on the github repository: https://github.com/frida/frida > [!WARNING] > You must do the following steps if you are using Windows 11 > > 1. Open Windows Security > 2. App & Browser Control>Exploit Protection>Programme Settings > 3. Press the plus, Select program by name and enter `lsass.exe` > 4. Scroll down until you see `Hardware Enforced Stack Protection` > 5. Copy these settings: > > ![Settings](Settings.png) You also need a script to extract data from lsass. Download 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 ##### Wireshark 1. Install wireshark using the installer on the [website](https://www.wireshark.org/download.html) 2. Make an empty file on the root of your C: drive `C:\keylog.log` 3. Open wireshark and go to Edit>Preferences 4. Click on Name Resolution and make sure your settings are set like this: ![Wireshark 1](Wireshark1.png) 5. Go to Protocols>TLS and then select `C:keylog.log` as (Pre)-Master-Secret log filename ##### Windows Settings 1. Press Win+R and type in `services.msc` 2. Open this and scroll until you see `Connected User Experiences and Telemetry` 3. Double click this and change the `Startup Type` to `Automatic` 4. Restart (maybe?) #### Viewing Events This section explains how to use Wireshark and Frida to view events from an event-based game. 1. Open an administrator terminal 2. Run frida using the following command `frida lsass.exe -l \Path\To\keylog.js ` 3. You should get output like this: ![Frida 1](Frida1.png) 4. Open wireshark and select your network interface 5. Put the following string into your filter box `tls && http2 && ip.dst_host contains "cloudapp.azure.com"` 6. Open your game of choice and start doing things 7. Look back at wireshark and you will (hopefully) see requests to a few cloudapp.azure.com domains ![Wireshark 2](Wireshark2.png) 8. Now click on "DATA" and then navigate to `Uncompressed entity body` 9. You can now read events (yay) #### Sending Events This 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 To start off you will need an event to work with. Get one from the Viewing Events section ##### Headers Now not all headers are required. You only need the following headers: ![Thunder1](Thunder1.png) All headers other than `tickets` and `authxtoken` are static. ##### Body The 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. You 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 ##### Errors The 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. The same goes for not incrementing seq ## Using Events In The Tool ### Support Overview The tool supports manually defined achievement criteria and has a stats editor in progress. #### Obtaining Your Events Token To use the event-based functionality in the tool, you need to input your own events token. 1. Follow the guide for Viewing Events 2. Instead of clicking on `DATA` click on `HEADERS` 3. 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) 4. 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 5. Your copied token should look something like this `x:XBL3.0 x=somenumbers;a very large amount of letters` 6. You now have a usable Events Token to input into the tool ### Adding Your Own Game This section explains how to add support for a game, find the correct events and stats, and prepare a template and criteria. #### Good Luck Achievements These achievements work similarly to title-based games and only require an ID input into a generic "achievement unlocked" event. As an example here is the data section of a Gears of War 4 event called `Microsoft.XboxLive.T552499398.UnlockAchievement` ```json "data": { "baseType": "Microsoft.XboxLive.InGame", "baseData": { "name": "UnlockAchievement", "serviceConfigId": "780f0100-3c66-41ff-b8cc-964f20ee78c6", "playerSessionId": "11111111-1111-1111-1111-111111111111", "titleId": "552499398", "userId": "1234567890123456", "ver": 1, "properties": { "AchievementID": 1, "MatchId": "Front End", "MatchJoinId": 0 }, "measurements": { "ProgressPercent": 1 } } } ``` These 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. #### Bad Luck Achievements These 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. ```json "data": { "baseType": "Microsoft.XboxLive.InGame", "baseData": { "name": "MissionCompleted", "serviceConfigId": "77290100-225e-4768-9373-98164430a9f8", "titleId": "1144039928", "userId": "1234567890123456", "properties": { "Coop": 0, "DatePlayedUTC": 133577376536650000, "DifficultyId": 1, "GameCategoryId": 18, "HaloTitleId": "HaloReach", "Kills": 358, "MapId": 179, "MissionScore64": 235767, "NumPlayers": 1, "Penalties": 0, "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}", "SkullUsedFlags": 0, "TimePlayedMS": 92356, "TotalCoopMissionsComplete": 0, "TotalSoloMissionsComplete": 2 }, "measurements": { "Multiplier": 2.35, "SkullMultiplier": 1 } } } ``` As 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. #### Preparing a Template Each 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. You first need to anonymise the event using the [Anonymising Events](#anonymising-events) section Now you need to figure out which criteria you need to create and where you would need to place these criteria #### Preparing the Criteria The criteria replace certain strings within the template. ##### Standard Replace This is the most common type of replacement for criteria and the most simple. Example from Achievement 900 from halo MCC ```json "Replacement1": { "ReplacementType": "Replace", "Target": "REPLACEMAPID", "Replacement": 179 }, ``` This replaces the string `REPLACEMAPID` with the number `179` ##### Range Replace (Int) This 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. Example from Achievement 900 from halo MCC: ```json "Replacement3": { "ReplacementType": "RangeInt", "Target": "REPLACEMISSIONSCORE", "Min": 12750, "Max": 14999 }, ``` This replaces the string `REPLACEMISSIONSCORE` with a number between `12750` and `14999` ##### Range Replace (Float) This type of replacement is the same as the above but for Float numbers instead of Integers Example from Achievement 900 from halo MCC: ```json "Replacement5": { "ReplacementType": "RangeFloat", "Target": "REPLACEMULTIPLIER", "Min": 1, "Max": 2 }, ``` This replaces the string `REPLACEMULTIPLIER` with a float between `1` and `2` ##### Special Replacements These replacements are either required by the tool or are created as exceptions to fill out data otherwise impossible. They currently consist of: - REPLACEXUID (Required) - REPLACETIME (Required) - REPLACESEQ (Required) - LDAP timestamp (Exception: Created for Halo MCC mission completion timestamp) ##### Ordering Criteria The 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. #### Examples ##### Good Luck Achievement In this example, I will be using Gears Of War 4 ###### Step 1. Monitor an event of an achievement unlocking and anonymise that event data ```json { "ver": "4.0", "name": "Microsoft.XboxLive.T552499398.UnlockAchievement", "time": "REPLACETIME", "iKey": "o:0890af88a9ed4cc886a14f5e174a2827", "ext": { "utc": { "shellId": 1, "eventFlags": 1, "pgName": "XBOX", "flags": 1, "epoch": "1", "seq": REPLACESEQ }, "privacy": { "isRequired": false }, "metadata": { "f": { "baseData": { "f": { "properties": { "f": { "AchievementID": 1, "MatchJoinId": 1 } }, "measurements": { "f": { "ProgressPercent": 1 } } } } }, "policies": 0 }, "os": { "bootId": 1, "name": "1", "ver": "1", "expId": "1" }, "app": { "id": "U:Microsoft.SpartaUWP_14.4.0.2_x64__8wekyb3d8bbwe!GearGameShippingPublic", "ver": "14.4.0.2_x64_!2019/06/27:23:38:26!9474C5E!geargame.exe", "is1P": 1, "asId": 1 }, "device": { "localId": "s:11111111-1111-1111-1111-111111111111", "deviceClass": "Windows.Desktop" }, "protocol": { "devMake": "1", "devModel": "1", "ticketKeys": [ "1" ] }, "user": { "localId": "s:11111111-1111-1111-1111-111111111111" }, "loc": { "tz": "00:00" } }, "data": { "baseType": "Microsoft.XboxLive.InGame", "baseData": { "name": "UnlockAchievement", "serviceConfigId": "780f0100-3c66-41ff-b8cc-964f20ee78c6", "playerSessionId": "11111111-1111-1111-1111-111111111111", "titleId": "552499398", "userId": "REPLACEXUID", "ver": 1, "properties": { "AchievementID": 4, "MatchId": "11111111-1111-1111-1111-111111111111", "MatchJoinId": 0 }, "measurements": { "ProgressPercent": 1 } } } } ``` ###### Step 2. Figure out the important values within the data section In 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 ###### Step 3. Create the template Knowing that AchievementId is our only important value we can set up the template like this: ```json { "ver": "4.0", "name": "Microsoft.XboxLive.T552499398.UnlockAchievement", "time": "REPLACETIME", "iKey": "o:0890af88a9ed4cc886a14f5e174a2827", "ext": { "utc": { "shellId": 1, "eventFlags": 1, "pgName": "XBOX", "flags": 1, "epoch": "1", "seq": REPLACESEQ }, "privacy": { "isRequired": false }, "metadata": { "f": { "baseData": { "f": { "properties": { "f": { "AchievementID": 1, "MatchJoinId": 1 } }, "measurements": { "f": { "ProgressPercent": 1 } } } } }, "policies": 0 }, "os": { "bootId": 1, "name": "1", "ver": "1", "expId": "1" }, "app": { "id": "U:Microsoft.SpartaUWP_14.4.0.2_x64__8wekyb3d8bbwe!GearGameShippingPublic", "ver": "14.4.0.2_x64_!2019/06/27:23:38:26!9474C5E!geargame.exe", "is1P": 1, "asId": 1 }, "device": { "localId": "s:11111111-1111-1111-1111-111111111111", "deviceClass": "Windows.Desktop" }, "protocol": { "devMake": "1", "devModel": "1", "ticketKeys": [ "1" ] }, "user": { "localId": "s:11111111-1111-1111-1111-111111111111" }, "loc": { "tz": "00:00" } }, "data": { "baseType": "Microsoft.XboxLive.InGame", "baseData": { "name": "UnlockAchievement", "serviceConfigId": "780f0100-3c66-41ff-b8cc-964f20ee78c6", "playerSessionId": "11111111-1111-1111-1111-111111111111", "titleId": "552499398", "userId": "REPLACEXUID", "ver": 1, "properties": { "AchievementID": REPLACECRITERIA1, "MatchId": "11111111-1111-1111-1111-111111111111", "MatchJoinId": 0 }, "measurements": { "ProgressPercent": 1 } } } } ``` `REPLACECRITERIA1` is the only thing we need to change to unlock these Good Luck achievements. This file will be saved as the TitleID `552499398.json` ###### Step 4. Create the data Open `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 ![Data.json1](Data.json.png) First, add the title ID to `SupportedTitleIDs` ```json "SupportedTitleIDs": [ 1144039928, 552499398 ] ``` Secondly, create a new section within the json file ```json "552499398": { "FullySupported": false, "Achievements": { } } ``` The json file should now look like this: ![Data.json2](Data.json2.png) ###### Step 5. Add the achievement criteria We know that 4 matches up with achievement id 70 so we will add this ```json "552499398": { "FullySupported": false, "Achievements": { "70": { "CriteriaReplacement": { "ReplacementType": "Replace", "Target": "REPLACECRITERIA1", "Replacement": "4" } } } }, ``` ##### Bad Luck Achievement The majority of this guide is the same as Good Luck achievement so I will only go over the changes ###### Step 1. Monitor an event of an achievement unlocking and anonymise that event data ```json { "ver": "4.0", "name": "Microsoft.XboxLive.T1144039928.MissionCompleted", "time": "REPLACETIME", "iKey": "o:0890af88a9ed4cc886a14f5e174a2827", "ext": { "utc": { "shellId": 1, "eventFlags": 1, "pgName": "XBOX", "flags": 1, "epoch": "1", "seq": REPLACESEQ }, "privacy": { "dataType": 1, "isRequired": false }, "metadata": { "f": { "baseData": { "f": { "properties": { "f": { "AchievementId": 1 } }, "ver": 1 } } }, "privTags": 1, "policies": 0 }, "os": { "bootId": 1, "name": "1", "ver": "1", "expId": "1" }, "app": { "id": "U:Microsoft.Chelan_1.3385.0.0_x64__8wekyb3d8bbwe!HaloMCCShipping", "ver": "1.3385.0.0_x64_!2024/01/03:17:50:38!3E58AE2!mccwinstore-win64-shipping.exe", "is1P": 1, "asId": 1 }, "device": { "localId": "s:11111111-1111-1111-1111-111111111111", "deviceClass": "Windows.Desktop" }, "protocol": { "devMake": "1", "devModel": "1", "ticketKeys": [ "1" ] }, "user": { "localId": "m:11111111111111111" }, "loc": { "tz": "00:00" } }, "data": { "baseType": "Microsoft.XboxLive.InGame", "baseData": { "name": "MissionCompleted", "serviceConfigId": "77290100-225e-4768-9373-98164430a9f8", "titleId": "1144039928", "userId": "REPLACEXUID", "properties": { "Coop": 0, "DatePlayedUTC": 133583987328905914, "DifficultyId": 1, "GameCategoryId": 18, "HaloTitleId": "HaloReach", "Kills": 257, "MapId": 179, "MissionScore64": 13327, "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": 1014169, "TotalCoopMissionsComplete": 0, "TotalSoloMissionsComplete": 1 }, "measurements": { "Multiplier": 1.3916192, "SkullMultiplier": 1 } } } } ``` ###### Step 2. Figure out the important values within the data section There 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 ###### Step 3. Create the template Step not required as MCC is already supported ###### Step 4. Create the data Step not required as MCC is already supported ###### Step 5. Add the achievement criteria I 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)` ```json "900": { "EventReplacement": { "ReplacementType": "Replace", "Target": "REPLACEEVENT", "Replacement": "Microsoft.XboxLive.T1144039928.MissionCompleted" }, "DataReplacement": { "ReplacementType": "Replace", "Target": "REPLACEDATA", "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}}}" }, "Replacement1": { "ReplacementType": "Replace", "Target": "REPLACEMAPID", "Replacement": 179 }, "Replacement2": { "ReplacementType": "RangeInt", "Target": "REPLACEKILLS", "Min": 200, "Max": 300 }, "Replacement3": { "ReplacementType": "RangeInt", "Target": "REPLACEMISSIONSCORE", "Min": 12750, "Max": 14999 }, "Replacement4": { "ReplacementType": "RangeInt", "Target": "REPLACETIMEPLAYED", "Min": 900001, "Max": 1035000 }, "Replacement5": { "ReplacementType": "RangeFloat", "Target": "REPLACEMULTIPLIER", "Min": 1, "Max": 2 }, "Replacement6": { "ReplacementType": "StupidFuckingLDAPTimestamp", "Target": "REPLACEDATEPLAYED" } }, "915": { "EventReplacement": { "ReplacementType": "Replace", "Target": "REPLACEEVENT", "Replacement": "Microsoft.XboxLive.T1144039928.MissionCompleted" }, "DataReplacement": { "ReplacementType": "Replace", "Target": "REPLACEDATA", "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}}}" }, "Replacement1": { "ReplacementType": "Replace", "Target": "REPLACEMAPID", "Replacement": 179 }, "Replacement2": { "ReplacementType": "RangeInt", "Target": "REPLACEKILLS", "Min": 200, "Max": 300 }, "Replacement3": { "ReplacementType": "RangeInt", "Target": "REPLACEMISSIONSCORE", "Min": 12750, "Max": 14999 }, "Replacement4": { "ReplacementType": "RangeInt", "Target": "REPLACETIMEPLAYED", "Min": 765000, "Max": 899999 }, "Replacement5": { "ReplacementType": "RangeFloat", "Target": "REPLACEMULTIPLIER", "Min": 1, "Max": 2 }, "Replacement6": { "ReplacementType": "StupidFuckingLDAPTimestamp", "Target": "REPLACEDATEPLAYED" } }, "925": { "EventReplacement": { "ReplacementType": "Replace", "Target": "REPLACEEVENT", "Replacement": "Microsoft.XboxLive.T1144039928.MissionCompleted" }, "DataReplacement": { "ReplacementType": "Replace", "Target": "REPLACEDATA", "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}}}" }, "Replacement1": { "ReplacementType": "Replace", "Target": "REPLACEMAPID", "Replacement": 179 }, "Replacement2": { "ReplacementType": "RangeInt", "Target": "REPLACEKILLS", "Min": 200, "Max": 300 }, "Replacement3": { "ReplacementType": "RangeInt", "Target": "REPLACEMISSIONSCORE", "Min": 15001, "Max": 17250 }, "Replacement4": { "ReplacementType": "RangeInt", "Target": "REPLACETIMEPLAYED", "Min": 900001, "Max": 1035000 }, "Replacement5": { "ReplacementType": "RangeFloat", "Target": "REPLACEMULTIPLIER", "Min": 1, "Max": 2 }, "Replacement6": { "ReplacementType": "StupidFuckingLDAPTimestamp", "Target": "REPLACEDATEPLAYED" } } } ``` ### Stats Editor This section is under construction. ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: LICENSE.MIT ================================================ MIT License Copyright (c) 2021-2023 Leszek Pomianowski and WPF UI Contributors. https://dev.lepo.co/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Xbox Achievement Unlocker ![GitHub contributors][contributors-badge] ![GitHub forks][forks-badge] ![GitHub stars][stars-badge] ![GitHub issues][issues-badge] ![GitHub release][release-badge] [Join our Discord][discord-invite] Unlock achievements on Microsoft/Xbox games with ease. This tool is inspired by the functionality of Steam Achievements Manager and is completely free to use. ## Table of Contents - [Xbox Achievement Unlocker](#xbox-achievement-unlocker) - [Table of Contents](#table-of-contents) - [About Xbox Achievement Unlocker](#about-xbox-achievement-unlocker) - [How It Works](#how-it-works) - [Requirements](#requirements) - [Features](#features) - [Screenshots](#screenshots) - [Events Guide](#events-guide) - [Usage Guide](#usage-guide) - [Future Improvements](#future-improvements) - [Join Our Discord Server](#join-our-discord-server) - [License](#license) - [Sponsors](#sponsors) - [ziqnr](#ziqnr) ## About Xbox Achievement Unlocker There 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. ## How It Works Xbox 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. ## Requirements - [dotnet 8](https://download.visualstudio.microsoft.com/download/pr/77284554-b8df-4697-9a9e-4c70a8b35f29/6763c16069d1ab8fa2bc506ef0767366/dotnet-runtime-8.0.5-win-x64.exe) - [New Xbox app](https://apps.microsoft.com/store/detail/xbox/9MV0B5HZVK9Z) ## Features - Extract XAuth from Xbox app or use OAuth to login - Obtain a list of games from the user or any selected XUID - Unlock Achievements for any Title Managed game - Spoof time in any game - Automatic updates - Automatically spoof time in the currently viewed game - Search TA and use the Xbox API to get the titleID for any game with a store page ## Screenshots Coming soon. ## Events Guide See [Events](./Doc/Events.md) for details. ## Usage Guide See [Discord](https://discord.com/channels/1013602813093359657/1233193528553640017) for comprehensive guides by Xolara. ## Future Improvements - Stats editor - Support for Event based stats ## Join Our Discord Server Feel free to join our [Discord server][discord-invite] for updates and discussions. ## License The 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]. This tool uses the XboxAuthNet library which is also MIT licensed ## Sponsors Thanks very much to all of my sponsors. Below are messages included as one of the sponsorship rewards ### ziqnr "I have brain damage" - [ziqnr](https://github.com/ziqnr) 2024 [contributors-badge]: https://img.shields.io/github/contributors/ItsLogic/Xbox-Achievement-Unlocker?style=for-the-badge [contributors-url]: https://github.com/ItsLogic/Xbox-Achievement-Unlocker/graphs/contributors [forks-badge]: https://img.shields.io/github/forks/ItsLogic/Xbox-Achievement-Unlocker?style=for-the-badge [forks-url]: https://github.com/ItsLogic/Xbox-Achievement-Unlocker/network/members [stars-badge]: https://img.shields.io/github/stars/ItsLogic/Xbox-Achievement-Unlocker?style=for-the-badge [stars-url]: https://github.com/ItsLogic/Xbox-Achievement-Unlocker/stargazers [issues-badge]: https://img.shields.io/github/issues/ItsLogic/Xbox-Achievement-Unlocker?style=for-the-badge [issues-url]: https://github.com/ItsLogic/Xbox-Achievement-Unlocker/issues [release-badge]: https://img.shields.io/github/v/release/ItsLogic/Xbox-Achievement-Unlocker?style=for-the-badge [release-url]: https://github.com/ItsLogic/Xbox-Achievement-Unlocker/releases [discord-id]: https://img.shields.io/discord/1013602813093359657?logo=discord&style=for-the-badge [discord-invite]: https://discord.gg/ugDvSw7cns [WPF-Commit]: https://github.com/lepoco/wpfui/tree/c8cd75f6f82414a52a94d2a55fe2a21dd5db83d7 [LICENSE]:LICENSE [MIT-LICENSE]:LICENSE.MIT ================================================ FILE: XAU/App.xaml ================================================  ================================================ FILE: XAU/App.xaml.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Wpf.Ui.Contracts; using Wpf.Ui.Services; using XAU.Services; using XAU.ViewModels.Pages; using XAU.ViewModels.Windows; using XAU.Views.Pages; using XAU.Views.Windows; namespace XAU; public partial class App { private static readonly IHost Host = Microsoft.Extensions.Hosting.Host .CreateDefaultBuilder() .ConfigureServices((_, services) => { services.AddHostedService(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); }).Build(); private static T? GetService() where T : class { return Host.Services.GetService(typeof(T)) as T; } private void OnStartup(object sender, StartupEventArgs e) { Host.Start(); SetupExceptionHandling(); } private async void OnExit(object sender, ExitEventArgs e) { await Host.StopAsync(); Host.Dispose(); } private void SetupExceptionHandling() { AppDomain.CurrentDomain.UnhandledException += (_, e) => { ReportException((Exception)e.ExceptionObject); }; DispatcherUnhandledException += (_, e) => { ReportException(e.Exception); e.Handled = true; }; TaskScheduler.UnobservedTaskException += (_, e) => { ReportException(e.Exception); e.SetObserved(); }; } private static void ReportException(Exception exception) { var mainWindowViewModel = GetService(); mainWindowViewModel?.ShowErrorDialog(exception); } } ================================================ FILE: XAU/AssemblyInfo.cs ================================================ [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] ================================================ FILE: XAU/Models/GitHubResponse.cs ================================================ using Newtonsoft.Json; public class EventsUpdateResponse { public int Timestamp { get; set; } public string? DataVersion { get; set; } } public class VersionResponse { public string? DownloadURL { get; set; } public string? LatestBuildVersion { get; set; } } public class GitHubFile { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("path")] public string Path { get; set; } [JsonProperty("sha")] public string Sha { get; set; } [JsonProperty("size")] public long Size { get; set; } [JsonProperty("download_url")] public string DownloadUrl { get; set; } } ================================================ FILE: XAU/Models/MiscItems.cs ================================================ public class GameItem { public string Title { get; set; } public string TitleId { get; set; } public bool IsTitleBased { get; set; } } ================================================ FILE: XAU/Models/XAUSettings.cs ================================================ public class XAUSettings { public string? SettingsVersion { get; set; } public string? ToolVersion { get; set; } public bool UnlockAllEnabled { get; set; } public bool AutoSpooferEnabled { get; set; } public bool AutoLaunchXboxAppEnabled { get; set; } public bool LaunchHidden { get; set; } public bool FakeSignatureEnabled { get; set; } public bool RegionOverride { get; set; } public bool UseAcrylic { get; set; } public bool PrivacyMode { get; set; } public bool OAuthLogin { get; set; } public bool AutoGrabEventsToken { get; set; } public string? CachedEventsToken { get; set; } public DateTime? EventsTokenObtainedAt { get; set; } public string? EventsUserHash { get; set; } } ================================================ FILE: XAU/Models/XboxApiRequest.cs ================================================ // TODO: Clean up, set names, default fields, minor renames, etc. public class GameTitleRequest { public string? Pfns { get; set; } public List TitleIds { get; set; } = new List(); } public class AchievementsArrayEntry { public string? id { get; set; } public string percentComplete { get; set; } = "100"; } public class UnlockTitleBasedAchievementRequest { public string action { get; set; } = @"progressUpdate"; public string serviceConfigId { get; set; } = StringConstants.ZeroUid; public string? titleId { get; set; } public string? userId { get; set; } public List achievements { get; set; } = new List(); } public class GameStat { public string Name { get; set; } = "MinutesPlayed"; public string? TitleId { get; set; } } public class GameStatsRequest { public string ArrangeByField { get; set; } = "xuid"; public List Xuids { get; set; } = new List(); public List Stats { get; set; } = new List(); } public class HeartbeatRequest { public List titles { get; set; } = new List(); } public class TitleRequest { public int expiration { get; set; } = 600; public string? id { get; set; } public string state { get; set; } = "active"; public string sandbox { get; set; } = "RETAIL"; } public class GamepassProductsRequest { public List Products { get; set; } = new List(); } ================================================ FILE: XAU/Models/XboxApiResponse.cs ================================================ // OpenAPI or Swagger doesn't exist. But let's try and type the things we need to make parsing more readable // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/atoc-xboxlivews-reference // TODO: Clean up, set names, default fields, minor renames, etc. public class PersonResponse { public string? Xuid { get; set; } public bool IsFavorite { get; set; } public bool IsFollowingCaller { get; set; } public bool IsFollowedByCaller { get; set; } public bool IsIdentityShared { get; set; } public DateTime? AddedDateTimeUtc { get; set; } public string? DisplayName { get; set; } public string? RealName { get; set; } public string? DisplayPicRaw { get; set; } public string? ShowUserAsAvatar { get; set; } public string? Gamertag { get; set; } public string? GamerScore { get; set; } public string? ModernGamertag { get; set; } public string? ModernGamertagSuffix { get; set; } public string? UniqueModernGamertag { get; set; } public string? XboxOneRep { get; set; } public string? PresenceState { get; set; } public string? PresenceText { get; set; } public object? PresenceDevices { get; set; } public bool IsBroadcasting { get; set; } public bool? IsCloaked { get; set; } public bool IsQuarantined { get; set; } public bool IsXbox360Gamerpic { get; set; } public DateTime? LastSeenDateTimeUtc { get; set; } public object? Suggestion { get; set; } public object? Recommendation { get; set; } public object? Search { get; set; } public object? TitleHistory { get; set; } public MultiplayerSummary? MultiplayerSummary { get; set; } public object? RecentPlayer { get; set; } public object? Follower { get; set; } public PreferredColor? PreferredColor { get; set; } public List PresenceDetails { get; set; } = new List(); public object? TitlePresence { get; set; } public object? TitleSummaries { get; set; } public object? PresenceTitleIds { get; set; } public Detail? Detail { get; set; } public object? CommunityManagerTitles { get; set; } public object? SocialManager { get; set; } public object? Broadcast { get; set; } public object? Avatar { get; set; } public List LinkedAccounts { get; set; } = new List(); public string? ColorTheme { get; set; } public string? PreferredFlag { get; set; } public List PreferredPlatforms { get; set; } = new List(); } public class MultiplayerSummary { public List JoinableActivities { get; set; } = new List(); public List PartyDetails { get; set; } = new List(); public int InParty { get; set; } } public class PreferredColor { public string? PrimaryColor { get; set; } public string? SecondaryColor { get; set; } public string? TertiaryColor { get; set; } } public class PresenceDetail { public bool IsBroadcasting { get; set; } public string? Device { get; set; } public object? DeviceSubType { get; set; } public object? GameplayType { get; set; } public string? PresenceText { get; set; } public string? State { get; set; } public string? TitleId { get; set; } public object? TitleType { get; set; } public bool IsPrimary { get; set; } public bool IsGame { get; set; } public object? RichPresenceText { get; set; } } public class Detail { public string? AccountTier { get; set; } public string? Bio { get; set; } public bool IsVerified { get; set; } public string? Location { get; set; } public string? Tenure { get; set; } public List Watermarks { get; set; } = new List(); public bool Blocked { get; set; } public bool Mute { get; set; } public int FollowerCount { get; set; } public int FollowingCount { get; set; } public bool HasGamePass { get; set; } public List? Genres { get; set; } } public class LinkedAccount { public string? NetworkName { get; set; } public string? DisplayName { get; set; } public bool ShowOnProfile { get; set; } public bool IsFamilyFriendly { get; set; } public string? Deeplink { get; set; } } public class Profile { public List People { get; set; } = new List(); public object? RecommendationSummary { get; set; } public object? FriendFinderState { get; set; } public object? AccountLinkDetails { get; set; } } public class XboxTitle { public string? TitleId { get; set; } public string? Pfn { get; set; } public string? BingId { get; set; } public string? WindowsPhoneProductId { get; set; } public required string Name { get; set; } public string? Type { get; set; } public List Devices { get; set; } = new List(); public string? DisplayImage { get; set; } public string? MediaItemType { get; set; } public string? ModernTitleId { get; set; } public bool IsBundle { get; set; } public BasicAchievementDetails? Achievement { get; set; } public Stats? Stats { get; set; } public GamePass? GamePass { get; set; } public object? Images { get; set; } public object? TitleHistory { get; set; } public object? TitleRecord { get; set; } public object? Detail { get; set; } public object? FriendsWhoPlayed { get; set; } public object? AlternateTitleIds { get; set; } public object? ContentBoards { get; set; } public string? XboxLiveTier { get; set; } } public class BasicAchievementDetails { public int CurrentAchievements { get; set; } public int TotalAchievements { get; set; } public int CurrentGamerscore { get; set; } public int TotalGamerscore { get; set; } public double ProgressPercentage { get; set; } public int SourceVersion { get; set; } } public class Stats { public int SourceVersion { get; set; } } public class GamePass { public bool IsGamePass { get; set; } } public class GameTitle { public string? Xuid { get; set; } public List Titles { get; set; } = new List(); } public class GamepassData { public string? GamepassMembership { get; set; } } public class Gamepass { // This json response is actually massive, but we don't care // TOOD: maybe we want to look at points/stuff later public string? GamepassMembership { get; set; } public GamepassData? Data { get; set; } } public class TitleHistory { public DateTime LastTimePlayed { get; set; } public bool Visible { get; set; } public bool CanHide { get; set; } } public class Title { public string? TitleId { get; set; } public string? Pfn { get; set; } public string? BingId { get; set; } public string? ServiceConfigId { get; set; } public string? WindowsPhoneProductId { get; set; } public string? Name { get; set; } public string? Type { get; set; } public List Devices { get; set; } = new List(); public string? DisplayImage { get; set; } public string? MediaItemType { get; set; } public string? ModernTitleId { get; set; } public bool IsBundle { get; set; } public BasicAchievementDetails? Achievement { get; set; } public object? Stats { get; set; } public object? GamePass { get; set; } public object? Images { get; set; } public TitleHistory? TitleHistory { get; set; } public object? TitleRecord { get; set; } public Detail? Detail { get; set; } public object? FriendsWhoPlayed { get; set; } public object? AlternateTitleIds { get; set; } public object? ContentBoards { get; set; } public string? XboxLiveTier { get; set; } } public class TitlesList { public string? Xuid { get; set; } public List Titles { get; set; } = new List<Title>(); } public class ProfileSettings { public string? Id { get; set; } public string? Value { get; set; } } public class ProfileUser { public string? Id { get; set; } public string? HostId { get; set; } public List<ProfileSettings> Settings { get; set; } = new List<ProfileSettings>(); public string? IsSponsoredUser { get; set; } } public class BasicProfile { public List<ProfileUser> ProfileUsers { get; set; } = new List<ProfileUser>(); } public class Stat { public Dictionary<string, object> GroupProperties { get; set; } = new Dictionary<string, object>(); public string? Xuid { get; set; } public string? Scid { get; set; } public string? TitleId { get; set; } public string? Name { get; set; } public string? Type { get; set; } public string? Value { get; set; } public Dictionary<string, object> Properties { get; set; } = new Dictionary<string, object>(); } public class StatListCollection { public string? ArrangeByField { get; set; } public string? ArrangeByFieldId { get; set; } public List<Stat> Stats { get; set; } = new List<Stat>(); } public class GameStatsResponse { public List<object> Groups { get; set; } = new List<object>(); public List<StatListCollection> StatListsCollection { get; set; } = new List<StatListCollection>(); } public class AchievementRewards { public string? name { get; set; } public string? description { get; set; } public string? value { get; set; } public string? type { get; set; } public MediaAsset? mediaAsset { get; set; } public string? valueType { get; set; } } public class Rarity { public string? currentCategory { get; set; } public string? currentPercentage { get; set; } } public class AchievementRequirements { public string? id { get; set; } public string? current { get; set; } public string? target { get; set; } public string? operationType { get; set; } public string? valueType { get; set; } public string? ruleParticipationType { get; set; } } public class AchievementProgression { public List<AchievementRequirements> requirements { get; set; } = new List<AchievementRequirements>(); public string? timeUnlocked { get; set; } } public class TitleAssociation { public string? name { get; set; } public string? id { get; set; } } public class MediaAsset { public string? name { get; set; } public string? type { get; set; } public string? url { get; set; } } public class OneCoreAchievementResponse // Xbox One Achievements { public Rarity? rarity { get; set; } public object? gamerscore { get; set; } public required string id { get; set; } public string serviceConfigId { get; set; } = StringConstants.ZeroUid; public required string name { get; set; } public List<TitleAssociation> titleAssociations { get; set; } = new List<TitleAssociation>(); public string progressState { get; set; } = "Null"; public AchievementProgression? progression { get; set; } public List<MediaAsset> mediaAssets { get; set; } = new List<MediaAsset>(); public List<string> platforms { get; set; } = new List<string>(); public bool isSecret { get; set; } public string? description { get; set; } public string? lockedDescription { get; set; } public string? productId { get; set; } public string? achievementType { get; set; } public string? participationType { get; set; } public TimeWindow? timeWindow { get; set; } public List<AchievementRewards> rewards { get; set; } = new List<AchievementRewards>(); public string? estimatedTime { get; set; } public string? deeplink { get; set; } public string? isRevoked { get; set; } public string? raritycurrentCategory { get; set; } public string? raritycurrentPercentage { get; set; } } public class Xbox360AchievementEntry { public int id {get; set;} public long titleId {get; set;} public string name {get; set;} public int gamerscore {get; set;} public string description {get; set;} public string lockedDescription {get; set;} public string timeUnlocked {get; set;} public bool isSecret {get; set;} public Rarity? rarity {get; set;} } public class Xbox360AchievementResponse { public List<Xbox360AchievementEntry> achievements {get;set; } = new List<Xbox360AchievementEntry>(); } public class AchievementsResponse { public List<OneCoreAchievementResponse> achievements { get; set; } = new List<OneCoreAchievementResponse>(); } public class TimeWindow { public required string startDate { get; set; } public required string endDate { get; set; } } public class Image { public string? URI { get; set; } public int Height { get; set; } public int Width { get; set; } public string? Caption { get; set; } } public class Product { public bool PCPlatformPreinstallable { get; set; } public string? ProductTitle { get; set; } public string? ProductDescription { get; set; } public string? ProductDescriptionShort { get; set; } public string? PackageFamilyName { get; set; } public string? ProductType { get; set; } public object? ChildPackageFamilyNames { get; set; } public string? XboxTitleId { get; set; } public object? ChildXboxTitleIds { get; set; } public int MinimumUserAge { get; set; } public List<object> Children { get; set; } = new List<object>(); public List<string> Categories { get; set; } = new List<string>(); public List<object> Attributes { get; set; } = new List<object>(); public object? HeroTrailer { get; set; } public Image? ImageBoxArt { get; set; } public Image? ImageHero { get; set; } public object? ImageTitledHero { get; set; } public Image? ImagePoster { get; set; } public object? ImageTile { get; set; } public object? PCComingSoonDate { get; set; } public object? PCExitDate { get; set; } public object? UltimateComingSoonDate { get; set; } public object? UltimateExitDate { get; set; } public bool IsEAPlay { get; set; } public List<object> XCloudSupportedInputs { get; set; } = new List<object>(); public object? GameCatalogExtensionId { get; set; } public string? StoreId { get; set; } } public class GamePassProducts { public Dictionary<string, Product> Products { get; set; } = new Dictionary<string, Product>(); public List<object> InvalidIds { get; set; } = new List<object>(); } ================================================ FILE: XAU/Networking/DBoxRestAPI.cs ================================================ using System.Net; using System.Net.Http; using HtmlAgilityPack; using Newtonsoft.Json.Linq; public class DBoxRestApi { private readonly HttpClient _httpClient; public DBoxRestApi() { // This is a placeholder for the Xbox REST API var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }; _httpClient = new HttpClient(handler); } private void SetDefaultHeaders() { _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, HeaderValues.AcceptEncoding); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, HeaderValues.Accept); } public async Task<JObject> SearchAsync(string searchText) { _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json"); searchText = Uri.EscapeDataString(searchText); var response = await _httpClient.GetAsync($"https://dbox.tools/api/title_ids/?name={searchText}&limit=100&offset=0"); var jsonString = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { return JObject.Parse(jsonString); } else { throw new HttpRequestException($"Error fetching data: {response.StatusCode} - {jsonString}"); } } } ================================================ FILE: XAU/Networking/GithubRestApi.cs ================================================ using System.Net; using System.Net.Http; using Newtonsoft.Json; using Newtonsoft.Json.Linq; public class GithubRestApi { private readonly HttpClient _httpClient; // User specifics public GithubRestApi() { var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }; _httpClient = new HttpClient(handler); } private void SetDefaultHeaders() { _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0"); _httpClient.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, "gzip, deflate, br"); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"); } public async Task<VersionResponse?> GetDevToolVersionAsync() { SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.GitHubRaw); var responseString = await _httpClient.GetStringAsync("https://raw.githubusercontent.com/Fumo-Unlockers/Xbox-Achievement-Unlocker/Pre-Release/info.json"); return JsonConvert.DeserializeObject<VersionResponse>(responseString); } public async Task<dynamic> GetReleaseVersionAsync() { SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.GitHubApi); var responseString = await _httpClient.GetStringAsync("https://api.github.com/repos/Fumo-Unlockers/Xbox-Achievement-unlocker/releases"); var jsonResponse = (dynamic)JArray.Parse(responseString); return jsonResponse; } public async Task<EventsUpdateResponse?> CheckForEventUpdatesAsync() { SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.GitHubRaw); var responseString = await _httpClient.GetStringAsync("https://raw.githubusercontent.com/Fumo-Unlockers/Xbox-Achievement-Unlocker/Events-Data/meta.json"); return JsonConvert.DeserializeObject<EventsUpdateResponse>(responseString); } public async Task<GitHubFile?> GetXboxGamesDatabaseInfoAsync() { SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.GitHubApi); var responseString = await _httpClient.GetStringAsync("https://api.github.com/repos/Fumo-Unlockers/XboxGames/contents"); var files = JsonConvert.DeserializeObject<List<GitHubFile>>(responseString); return files?.FirstOrDefault(f => f.Name.Equals("xbox_games.db", StringComparison.OrdinalIgnoreCase)); } } ================================================ FILE: XAU/Networking/XboxRestApi.cs ================================================ using System.Net; using System.Net.Http; using System.Text; using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using XAU.ViewModels.Pages; using XAU.ViewModels.Windows; public class XboxRestAPI { private readonly HttpClient _httpClient; private readonly HttpClient _eventBasedClient; // Dumb, but needed for events for now private readonly HttpClient _spooferClient; // User specifics private readonly string _xauth; private readonly string _requestedResponseLanguage; public XboxRestAPI(string xauth) { _xauth = xauth; _requestedResponseLanguage = HomeViewModel.Settings.RegionOverride ? "en-GB" : System.Globalization.CultureInfo.CurrentCulture.Name; var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }; _httpClient = new HttpClient(handler); _spooferClient = new HttpClient(handler); var insecureEventsHandler = new HttpClientHandler() { AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate, //This is an absolutely terrible idea but the stupid fucking events API just cries about SSL errors ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }; _eventBasedClient = new HttpClient(insecureEventsHandler); } private void SetDefaultHeaders() { _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Authorization, _xauth); _httpClient.DefaultRequestHeaders.Add(HeaderNames.AcceptLanguage, _requestedResponseLanguage); _httpClient.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, HeaderValues.AcceptEncoding); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, HeaderValues.Accept); #if DEBUG Console.WriteLine("Headers in _httpClient:"); foreach (var header in _httpClient.DefaultRequestHeaders) { if (header.Key == "Authorization") continue; Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}"); } #endif } private void SetDefaultSpooferHeaders() { _spooferClient.DefaultRequestHeaders.Clear(); _spooferClient.DefaultRequestHeaders.Add(HeaderNames.Authorization, _xauth); _spooferClient.DefaultRequestHeaders.Add(HeaderNames.AcceptLanguage, _requestedResponseLanguage); _spooferClient.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, HeaderValues.AcceptEncoding); _spooferClient.DefaultRequestHeaders.Add(HeaderNames.Accept, HeaderValues.Accept); #if DEBUG Console.WriteLine("Headers in _spooferClient:"); foreach (var header in _spooferClient.DefaultRequestHeaders) { if (header.Key == "Authorization") continue; Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}"); } #endif } private void SetDefaultEventBasedHeaders() { _eventBasedClient.DefaultRequestHeaders.Clear(); _eventBasedClient.DefaultRequestHeaders.Add("user-agent", "MSDW"); _eventBasedClient.DefaultRequestHeaders.Add("cache-control", "no-cache"); _eventBasedClient.DefaultRequestHeaders.Add(HeaderNames.Accept, HeaderValues.Accept); _eventBasedClient.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, HeaderValues.AcceptEncoding); _eventBasedClient.DefaultRequestHeaders.Add("reliability-mode", "standard"); _eventBasedClient.DefaultRequestHeaders.Add("client-version", "EUTC-Windows-C++-no-10.0.22621.3296.amd64fre.ni_release.220506-1250-no"); _eventBasedClient.DefaultRequestHeaders.Add("apikey", "0890af88a9ed4cc886a14f5e174a2827-9de66c5e-f867-43a8-a7b8-e0ddd481cca4-7548,95c1f21d6cb047a09e7b423c1cb2222e-9965f07b-54fa-498e-9727-9e8d24dec39e-7027"); _eventBasedClient.DefaultRequestHeaders.Add("Client-Id", "NO_AUTH"); _eventBasedClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.Telemetry); _eventBasedClient.DefaultRequestHeaders.Add(HeaderNames.Connection, "close"); ; var authxtoken = Regex.Replace(_xauth, @"XBL3\.0 x=\d+;", "XBL3.0 x=-;"); _eventBasedClient.DefaultRequestHeaders.Add("authxtoken", authxtoken); #if DEBUG Console.WriteLine("Headers in _eventBasedClient:"); foreach (var header in _eventBasedClient.DefaultRequestHeaders) { if (header.Key == "authxtoken") continue; Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}"); } #endif } public async Task<BasicProfile?> GetBasicProfileAsync() { SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion2); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.Profile); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Connection, HeaderValues.KeepAlive); var response = await _httpClient.GetStringAsync(BasicXboxAPIUris.GamertagUrl); return JsonConvert.DeserializeObject<BasicProfile>(response); } public async Task<Profile?> GetProfileAsync(string xuid) { SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion5); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.PeopleHub); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Connection, HeaderValues.KeepAlive); var responseString = await _httpClient.GetStringAsync(string.Format(InterpolatedXboxAPIUrls.ProfileUrl, xuid)); return JsonConvert.DeserializeObject<Profile>(responseString); } public async Task<GameTitle?> GetGameTitleAsync(string xuid, string titleId) { if (string.IsNullOrWhiteSpace(xuid) || string.IsNullOrWhiteSpace(titleId)) { // Don't send a request if we don't have the details return null; } SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion2); var gameTitleRequest = new GameTitleRequest() { Pfns = null, TitleIds = new List<string>() { titleId } }; var gameTitleHttpResponse = await _httpClient.PostAsync(string.Format(InterpolatedXboxAPIUrls.TitleUrl, xuid), new StringContent(JsonConvert.SerializeObject(gameTitleRequest), Encoding.UTF8, HeaderValues.Accept)); var gameTitleResponse = await gameTitleHttpResponse.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<GameTitle>(gameTitleResponse); } public async Task<Gamepass?> GetGamepassMembershipAsync(string xuid) { if (string.IsNullOrWhiteSpace(xuid)) { // Don't send a request if we don't have the details return null; } SetDefaultHeaders(); var gpuHttpResponse = await _httpClient.GetAsync(string.Format(InterpolatedXboxAPIUrls.GamepassMembershipUrl, xuid)); var gpuResponse = await gpuHttpResponse.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<Gamepass>(gpuResponse); } public async Task<TitlesList?> GetGamesListAsync(string xuid) { if (string.IsNullOrWhiteSpace(xuid)) { // Don't send a request if we don't have the details return null; } SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion2); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.TitleHub); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Connection, HeaderValues.KeepAlive); var responseString = await _httpClient.GetStringAsync(string.Format(InterpolatedXboxAPIUrls.TitlesUrl, xuid)); return JsonConvert.DeserializeObject<TitlesList>(responseString); } public async Task<JObject?> GetGamertagProfileAsync(string gamertag) { if (string.IsNullOrWhiteSpace(gamertag)) { // Don't send a request if we don't have the details return null; } SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion2); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.Profile); string url = string.Format(InterpolatedXboxAPIUrls.GamertagSearch, gamertag); var response = await _httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); var jsonResponse = await response.Content.ReadAsStringAsync(); return JObject.Parse(jsonResponse); } public async Task<GameStatsResponse?> GetGameStatsAsync(string xuid, string titleId) { if (string.IsNullOrWhiteSpace(xuid) || string.IsNullOrWhiteSpace(titleId)) { // Don't send a request if we don't have the details return null; } SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion2); var stat = new GameStat() { TitleId = titleId }; var gameStatsRequest = new GameStatsRequest() { Xuids = new List<string>() { xuid }, Stats = new List<GameStat>() { stat } }; var httpResponse = await _httpClient .PostAsync(BasicXboxAPIUris.UserStatsUrl, new StringContent(JsonConvert.SerializeObject(gameStatsRequest), Encoding.UTF8, HeaderValues.Accept)); var response = await httpResponse.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<GameStatsResponse>(response); } public async Task SendHeartbeatAsync(string xuid, string spoofedTitleId) { if (string.IsNullOrWhiteSpace(xuid) || string.IsNullOrWhiteSpace(spoofedTitleId)) { // Don't send a request if we don't have the details return; } SetDefaultSpooferHeaders(); _spooferClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion3); var heartbeatRequest = new HeartbeatRequest() { titles = new List<TitleRequest>() { new TitleRequest() { id = spoofedTitleId } } }; await _spooferClient.PostAsync( string.Format(InterpolatedXboxAPIUrls.HeartbeatUrl, xuid), new StringContent(JsonConvert.SerializeObject(heartbeatRequest), Encoding.UTF8, HeaderValues.Accept)); } public async Task StopHeartbeatAsync(string xuid) { if (string.IsNullOrWhiteSpace(xuid)) { // Don't send a request if we don't have the details return; } SetDefaultSpooferHeaders(); _spooferClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion3); await _spooferClient.DeleteAsync(string.Format(InterpolatedXboxAPIUrls.HeartbeatUrl, xuid)); } public async Task<AchievementsResponse?> GetAchievementsForTitleAsync(string xuid, string titleId) { if (string.IsNullOrWhiteSpace(xuid) || string.IsNullOrWhiteSpace(titleId)) { // Don't send a request if we don't have the details return null; } SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion4); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.Achievements); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Connection, HeaderValues.KeepAlive); var httpResponse = await _httpClient.GetAsync(string.Format(InterpolatedXboxAPIUrls.QueryAchievementsUrl, xuid, titleId)); var response = await httpResponse.Content.ReadAsStringAsync(); var achievements = JsonConvert.DeserializeObject<AchievementsResponse>(response); return achievements; } public async Task<Xbox360AchievementResponse?> GetAchievementsFor360TitleAsync(string xuid, string titleId) { if (string.IsNullOrWhiteSpace(xuid) || string.IsNullOrWhiteSpace(titleId)) { // Don't send a request if we don't have the details return null; } SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion3); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.Achievements); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Connection, HeaderValues.KeepAlive); var httpResponse = await _httpClient.GetAsync(string.Format(InterpolatedXboxAPIUrls.QueryAchievements360Url, xuid, titleId)); var response = await httpResponse.Content.ReadAsStringAsync(); var achievements = JsonConvert.DeserializeObject<Xbox360AchievementResponse>(response); return achievements; } public async Task UnlockTitleBasedAchievementAsync(string serviceConfigId, string titleId, string xuid, string achievementId, bool useFakeSignature = false) { // only unlock the specified achievement await UnlockTitleBasedAchievementsAsync(serviceConfigId, titleId, xuid, new List<string>() { achievementId }, useFakeSignature); } public async Task UnlockTitleBasedAchievementsAsync(string serviceConfigId, string titleId, string xuid, List<string> achievementIds, bool useFakeSignature = false) { if (string.IsNullOrWhiteSpace(serviceConfigId) || string.IsNullOrWhiteSpace(titleId) || string.IsNullOrWhiteSpace(xuid) || achievementIds.Count == 0) { // Don't send a request if we don't have the details return; } SetDefaultHeaders(); _httpClient.DefaultRequestHeaders.Add(HeaderNames.ContractVersion, HeaderValues.ContractVersion2); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Host, Hosts.Achievements); _httpClient.DefaultRequestHeaders.Add(HeaderNames.Connection, HeaderValues.KeepAlive); _httpClient.DefaultRequestHeaders.Add("User-Agent", "XboxServicesAPI/2021.10.20211005.0 c"); if (useFakeSignature) { _httpClient.DefaultRequestHeaders.Add(HeaderNames.Signature, HeaderValues.Signature); } // Split the requests into 50 achievements each. Anything over 100 seems to BadRequest. TODO: look into // headers and see if we can send long data or w/e const int chunkSize = 50; for (int i = 0; i < achievementIds.Count; i += chunkSize) { var chunk = achievementIds.Skip(i).Take(chunkSize).ToList(); var unlockRequest = new UnlockTitleBasedAchievementRequest { titleId = titleId, serviceConfigId = serviceConfigId, userId = xuid, achievements = chunk.Select(id => new AchievementsArrayEntry { id = id, percentComplete = "100" }).ToList() }; var unlockBodyStr = JsonConvert.SerializeObject(unlockRequest); var bodyconverted = new StringContent(unlockBodyStr, Encoding.UTF8, HeaderValues.Accept); var response = await _httpClient.PostAsync( string.Format(InterpolatedXboxAPIUrls.UpdateAchievementsUrl, xuid, serviceConfigId), bodyconverted); if (response.StatusCode != HttpStatusCode.OK) { throw new HttpRequestException($"Failed to unlock achievement(s) for title {titleId} with status code {response.StatusCode}"); } } } // TODO: see if we can handle the actual request body building public async Task UnlockEventBasedAchievement(string eventsToken, StringContent requestBody) { if (string.IsNullOrWhiteSpace(eventsToken)) { // Don't send a request if we don't have the details return; } SetDefaultEventBasedHeaders(); _eventBasedClient.DefaultRequestHeaders.Add("tickets", $"\"1\"=\"{eventsToken}\""); var response = await _eventBasedClient.PostAsync(BasicXboxAPIUris.TelemetryUrl, requestBody); var responseBody = await response.Content.ReadAsStringAsync(); HomeViewModel.EventsLog($"POST {BasicXboxAPIUris.TelemetryUrl} => {(int)response.StatusCode} {response.StatusCode}"); HomeViewModel.EventsLog($"Response: {responseBody}"); if (!response.IsSuccessStatusCode) { HomeViewModel.EventsLog("Response headers:"); foreach (var header in response.Headers) HomeViewModel.EventsLog($" {header.Key}: {string.Join(", ", header.Value)}"); } } public async Task<GamePassProducts?> GetTitleIdsFromGamePass(string prodId) { if (string.IsNullOrWhiteSpace(prodId)) { // Don't send a request if we don't have the details return null; } SetDefaultHeaders(); GamepassProductsRequest gamepassProducts = new GamepassProductsRequest() { Products = new List<string>() { prodId } }; var titleIDsHttpResponse = await _httpClient.PostAsync( BasicXboxAPIUris.GamepassCatalogUrl, new StringContent(JsonConvert.SerializeObject(gamepassProducts))); var titleIDsResponse = await titleIDsHttpResponse.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<GamePassProducts>(titleIDsResponse); } } ================================================ FILE: XAU/Services/ApplicationHostService.cs ================================================ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using XAU.Views.Pages; using XAU.Views.Windows; using Application = System.Windows.Application; namespace XAU.Services { /// <summary> /// Managed host of the application. /// </summary> public class ApplicationHostService : IHostedService { private readonly IServiceProvider _serviceProvider; public ApplicationHostService(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } /// <summary> /// Triggered when the application host is ready to start the service. /// </summary> /// <param name="cancellationToken">Indicates that the start process has been aborted.</param> public async Task StartAsync(CancellationToken cancellationToken) { await HandleActivationAsync(); } /// <summary> /// Triggered when the application host is performing a graceful shutdown. /// </summary> /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param> public async Task StopAsync(CancellationToken cancellationToken) { await Task.CompletedTask; } /// <summary> /// Creates main window during activation. /// </summary> private async Task HandleActivationAsync() { await Task.CompletedTask; if (!Application.Current.Windows.OfType<MainWindow>().Any()) { var navigationWindow = _serviceProvider.GetRequiredService<MainWindow>(); navigationWindow.Loaded += OnNavigationWindowLoaded; navigationWindow.Show(); } } private void OnNavigationWindowLoaded(object sender, RoutedEventArgs e) { if (sender is not MainWindow navigationWindow) { return; } navigationWindow.NavigationView.Navigate(typeof(HomePage)); } } } ================================================ FILE: XAU/Services/HttpServer/AchievementRoutes.cs ================================================ using Newtonsoft.Json; using System.IO; using System.Net; using System.Net.Http; using System.Text; public static class AchievementRoutes { private static readonly Dictionary<string, string> ServiceConfigCache = new(); public static Dictionary<string, Func<HttpListenerContext, Task>> GetRoutes(Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly) { return new Dictionary<string, Func<HttpListenerContext, Task>> { { "/api/achievements/unlockall", async context => await UnlockAllAchievementsRequest(context, getXboxRestAPI, getXUIDOnly) }, { "/api/achievements/unlock", async context => await UnlockAchievementRequest(context, getXboxRestAPI, getXUIDOnly) }, { "/api/achievements/", async context => await AchievementsTitleRequest(context, getXboxRestAPI, getXUIDOnly) } }; } private static async Task AchievementsTitleRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly) { var request = context.Request; var response = context.Response; try { if (request.Url.Segments.Length < 3) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No Title ID provided in URL" }); return; } string titleId = request.Url.Segments.Last().TrimEnd('/'); string xuid = getXUIDOnly?.Invoke(); if (string.IsNullOrWhiteSpace(xuid)) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No XUID found" }); return; } var xboxRestAPI = getXboxRestAPI(); var achievements = await xboxRestAPI.GetAchievementsForTitleAsync(xuid, titleId); if (achievements == null || achievements.achievements == null || !achievements.achievements.Any()) { var xbox360Achievements = await xboxRestAPI.GetAchievementsFor360TitleAsync(xuid, titleId); if (xbox360Achievements != null) { await SendJsonResponse(response, xbox360Achievements); return; } } if (achievements == null) { response.StatusCode = 404; await SendJsonResponse(response, new { error = "Achievements not found" }); return; } await SendJsonResponse(response, achievements); } catch (Exception ex) { response.StatusCode = 500; await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message }); } } private static async Task UnlockAchievementRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly) { var request = context.Request; var response = context.Response; try { if (request.HttpMethod != "POST") { response.StatusCode = 405; await SendJsonResponse(response, new { error = "Method not allowed. Use POST." }); return; } using var reader = new StreamReader(request.InputStream); var body = await reader.ReadToEndAsync(); var unlockRequest = JsonConvert.DeserializeObject<UnlockAchievementRequest>(body); if (unlockRequest == null || string.IsNullOrWhiteSpace(unlockRequest.TitleId) || string.IsNullOrWhiteSpace(unlockRequest.AchievementId)) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "Invalid request. Provide TitleId and AchievementId in your body." }); return; } string xuid = getXUIDOnly?.Invoke(); if (string.IsNullOrWhiteSpace(xuid)) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No XUID found" }); return; } string serviceConfigId = await GetServiceConfigId(getXboxRestAPI, getXUIDOnly, unlockRequest.TitleId); var xboxRestAPI = getXboxRestAPI(); await xboxRestAPI.UnlockTitleBasedAchievementAsync( serviceConfigId, unlockRequest.TitleId, xuid, unlockRequest.AchievementId ); await SendJsonResponse(response, new { message = "Achievement unlocked successfully", achievementId = unlockRequest.AchievementId, titleId = unlockRequest.TitleId }); } catch (Exception ex) { response.StatusCode = 500; await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message }); } } private static async Task UnlockAllAchievementsRequest(HttpListenerContext context, Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly) { var request = context.Request; var response = context.Response; try { if (request.HttpMethod != "POST") { response.StatusCode = 405; await SendJsonResponse(response, new { error = "Method not allowed. Use POST." }); return; } using var reader = new StreamReader(request.InputStream); var body = await reader.ReadToEndAsync(); var unlockRequest = JsonConvert.DeserializeObject<UnlockAllAchievementsRequest>(body); if (unlockRequest == null || string.IsNullOrWhiteSpace(unlockRequest.TitleId)) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "Invalid request. Provide TitleId in your body." }); return; } string serviceConfigId = await GetServiceConfigId(getXboxRestAPI, getXUIDOnly, unlockRequest.TitleId); string xuid = getXUIDOnly?.Invoke(); if (string.IsNullOrWhiteSpace(xuid)) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No XUID found" }); return; } var xboxRestAPI = getXboxRestAPI(); var achievements = await xboxRestAPI.GetAchievementsForTitleAsync(xuid, unlockRequest.TitleId); var achievementIds = achievements.achievements.Select(a => a.id).ToList(); await xboxRestAPI.UnlockTitleBasedAchievementsAsync( serviceConfigId, unlockRequest.TitleId, xuid, achievementIds ); await SendJsonResponse(response, new { message = "All achievements unlocked successfully", titleId = unlockRequest.TitleId, totalAchievements = achievementIds.Count }); } catch (Exception ex) { response.StatusCode = 500; await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message }); } } private static async Task<string> GetServiceConfigId(Func<XboxRestAPI> getXboxRestAPI, Func<string> getXUIDOnly, string titleId) { if (ServiceConfigCache.TryGetValue(titleId, out var cachedServiceConfigId)) { return cachedServiceConfigId; } var xuid = getXUIDOnly?.Invoke(); if (string.IsNullOrWhiteSpace(xuid)) throw new Exception("No XUID found."); var xboxRestAPI = getXboxRestAPI(); var achievements = await xboxRestAPI.GetAchievementsForTitleAsync(xuid, titleId); if (achievements == null || achievements.achievements == null || !achievements.achievements.Any()) { throw new Exception("Achievements not found for the title."); } var serviceConfigId = achievements.achievements.FirstOrDefault()?.serviceConfigId; if (string.IsNullOrWhiteSpace(serviceConfigId)) { throw new Exception("ServiceConfigId not found for the title."); } ServiceConfigCache[titleId] = serviceConfigId; return serviceConfigId; } private static async Task SendJsonResponse(HttpListenerResponse response, object data) { var json = JsonConvert.SerializeObject(data); var buffer = Encoding.UTF8.GetBytes(json); response.ContentLength64 = buffer.Length; response.ContentType = "application/json"; await response.OutputStream.WriteAsync(buffer); response.Close(); } } public class UnlockAchievementRequest { public string TitleId { get; set; } public string AchievementId { get; set; } } public class UnlockAllAchievementsRequest { public string TitleId { get; set; } } ================================================ FILE: XAU/Services/HttpServer/EndpointRoutes.cs ================================================ using Newtonsoft.Json; using System.Net; using System.Text; public static class EndpointRoutes { public static Dictionary<string, Func<HttpListenerContext, Task>> GetRoutes() { return new Dictionary<string, Func<HttpListenerContext, Task>> { { "/api/", async context => await IndexPageRequest(context) }, { "/", async context => await IndexPageRequest(context) } }; } private static async Task IndexPageRequest(HttpListenerContext context) { var response = context.Response; response.ContentType = "application/json"; string htmlContent = @" <!-- meow meow :) --> <!DOCTYPE html> <html lang=""en""> <head> <meta charset=""UTF-8""> <meta name=""viewport"" content=""width=device-width, initial-scale=1.0""> <title>XAU API Endpoints

XAU API Endpoints

Warning: These endpoints are still in beta and have not been extensively tested. Use at your own risk!

GET

/api/profile/me

Get the current user's profile information

Open Endpoint
GET

/api/profile/xuid/{xuid}

Get profile by XUID

GET

/api/profile/gt/{gamertag}

Get profile by Gamertag

GET

/api/games/me

Get the current user's games list

Open Endpoint
GET

/api/games/xuid/{xuid}

Get games list by XUID

GET

/api/games/gt/{gamertag}

Get games list by Gamertag

GET

/api/xauth

Get XAuth token

Plain Text Format JSON Format
GET

/api/spoof/{titleid}

Spoofs the specified title for 5 minutes. Make an API request every 5 minutes to keep the spoofer working.

GET

/api/achievements/{titleid}

Get achievements for a specific title

POST

/api/achievements/unlock

Unlock achievements of title-based game by ID.

                        {""titleId"": ""xxxxx"",""achievementId"": ""xxx""}
                    
POST

/api/achievements/unlockall

⚠️ BEWARE: Unlocks ALL achievements of a game ⚠️

                         {""TitleId"": ""xxxxx""} 
                    
GET

/api/games/search/titleid/{titleid}

Retrieve game details for a specific Title ID.

GET

/api/games/search/productid/{productid}

Retrieve game details for a specific product ID.

"; var buffer = Encoding.UTF8.GetBytes(htmlContent); response.ContentLength64 = buffer.Length; response.ContentType = "text/html"; await response.OutputStream.WriteAsync(buffer); response.Close(); } } ================================================ FILE: XAU/Services/HttpServer/GameRoutes.cs ================================================ using Newtonsoft.Json; using System.Net; using System.Text; public static class GameRoutes { public static Dictionary> GetRoutes(Func getXboxRestAPI, Func getXUIDOnly) { return new Dictionary> { { "/api/games/me", async context => await GamesMeRequest(context, getXboxRestAPI, getXUIDOnly) }, { "/api/games/xuid/", async context => await GamesXuidRequest(context, getXboxRestAPI) }, { "/api/games/gt/", async context => await GamesGamertagRequest(context, getXboxRestAPI) }, { "/api/games/search/titleid/", async context => await GameTitleIDSearchRequest(context, getXboxRestAPI, getXUIDOnly) }, { "/api/games/search/productid/", async context => await GameProductIDSearchRequest(context, getXboxRestAPI) } }; } private static async Task GamesMeRequest(HttpListenerContext context, Func getXboxRestAPI, Func getXUIDOnly) { var response = context.Response; try { string xuid = getXUIDOnly?.Invoke(); if (string.IsNullOrWhiteSpace(xuid)) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No XUID found" }); return; } var xboxRestAPI = getXboxRestAPI(); var gamesList = await xboxRestAPI.GetGamesListAsync(xuid); if (gamesList == null) { response.StatusCode = 404; await SendJsonResponse(response, new { error = "Games list not found" }); return; } await SendJsonResponse(response, gamesList); } catch (Exception ex) { response.StatusCode = 500; await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message }); } } private static async Task GamesXuidRequest(HttpListenerContext context, Func getXboxRestAPI) { var request = context.Request; var response = context.Response; try { if (request.Url.Segments.Length < 3) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No XUID provided in URL" }); return; } string xuid = request.Url.Segments.Last().TrimEnd('/'); var xboxRestAPI = getXboxRestAPI(); var gamesList = await xboxRestAPI.GetGamesListAsync(xuid); if (gamesList == null) { response.StatusCode = 404; await SendJsonResponse(response, new { error = "Games list not found" }); return; } await SendJsonResponse(response, gamesList); } catch (Exception ex) { response.StatusCode = 500; await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message }); } } private static async Task GamesGamertagRequest(HttpListenerContext context, Func getXboxRestAPI) { var request = context.Request; var response = context.Response; try { if (request.Url.Segments.Length < 3) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No Gamertag provided in URL" }); return; } string gamertag = request.Url.Segments.Last().TrimEnd('/'); var xboxRestAPI = getXboxRestAPI(); var gamertagProfile = await xboxRestAPI.GetGamertagProfileAsync(gamertag); if (gamertagProfile == null || !gamertagProfile.ContainsKey("profileUsers")) { response.StatusCode = 404; await SendJsonResponse(response, new { error = "Gamertag not found" }); return; } string xuid = gamertagProfile["profileUsers"][0]["id"].ToString(); var gamesList = await xboxRestAPI.GetGamesListAsync(xuid); if (gamesList == null) { response.StatusCode = 404; await SendJsonResponse(response, new { error = "Games list not found" }); return; } await SendJsonResponse(response, gamesList); } catch (Exception ex) { response.StatusCode = 500; await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message }); } } private static async Task GameTitleIDSearchRequest(HttpListenerContext context, Func getXboxRestAPI, Func getXUIDOnly) { var request = context.Request; var response = context.Response; try { if (request.Url.Segments.Length < 4) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No Title ID provided in URL" }); return; } string titleId = request.Url.Segments.Last().TrimEnd('/'); string xuid = getXUIDOnly?.Invoke(); if (string.IsNullOrWhiteSpace(xuid)) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No XUID found" }); return; } var xboxRestAPI = getXboxRestAPI(); var gameTitle = await xboxRestAPI.GetGameTitleAsync(xuid, titleId); if (gameTitle == null) { response.StatusCode = 404; await SendJsonResponse(response, new { error = "No Games found" }); return; } await SendJsonResponse(response, gameTitle); } catch (Exception ex) { response.StatusCode = 500; await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message }); } } private static async Task GameProductIDSearchRequest(HttpListenerContext context, Func getXboxRestAPI) { var request = context.Request; var response = context.Response; try { if (request.Url.Segments.Length < 4) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No Product ID provided in URL" }); return; } string productId = request.Url.Segments.Last().TrimEnd('/'); var xboxRestAPI = getXboxRestAPI(); var gamePassProducts = await xboxRestAPI.GetTitleIdsFromGamePass(productId); if (gamePassProducts == null) { response.StatusCode = 404; await SendJsonResponse(response, new { error = "No Games found" }); return; } await SendJsonResponse(response, gamePassProducts); } catch (Exception ex) { response.StatusCode = 500; await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message }); } } private static async Task SendJsonResponse(HttpListenerResponse response, object data) { var json = JsonConvert.SerializeObject(data); var buffer = Encoding.UTF8.GetBytes(json); response.ContentLength64 = buffer.Length; response.ContentType = "application/json"; await response.OutputStream.WriteAsync(buffer); response.Close(); } } ================================================ FILE: XAU/Services/HttpServer/HttpServer.cs ================================================ using System.Diagnostics; using System.Net; using System.Security.Principal; using System.Runtime.Versioning; using System.Net.NetworkInformation; using System.Net.Sockets; namespace XAU.Services.HttpServer { public sealed class HttpServer : IDisposable { private readonly HttpListener _listener; private readonly Dictionary> _routes; private string _port; private bool _isRunning; private bool _disposed; private const string FirewallRuleName = "XAU API Server"; public HttpServer(string port, Dictionary> routes) { _port = port; _routes = routes; _listener = new HttpListener(); UpdateListenerPrefixes(); } private static bool IsAdministrator() { using var identity = WindowsIdentity.GetCurrent(); var principal = new WindowsPrincipal(identity); return principal.IsInRole(WindowsBuiltInRole.Administrator); } [SupportedOSPlatform("windows")] public void RestartAsAdmin() { var startInfo = new ProcessStartInfo { UseShellExecute = true, WorkingDirectory = Environment.CurrentDirectory, FileName = Process.GetCurrentProcess().MainModule?.FileName, Verb = "runas" }; Process.Start(startInfo); Environment.Exit(0); } private void UpdateListenerPrefixes() { _listener.Prefixes.Clear(); _listener.Prefixes.Add($"http://localhost:{_port}/"); if (IsAdministrator()) { // Admin required for other PCs on the network to access API (ex: XAU Mobile) _listener.Prefixes.Add($"http://*:{_port}/"); } } public static void AddFirewallRule(string port) { if (RuleExists(port)) return; var process = new Process { StartInfo = new ProcessStartInfo { FileName = "netsh", Arguments = $"advfirewall firewall add rule name=\"{FirewallRuleName}\" dir=in action=allow protocol=TCP localport={port}", Verb = "runas", UseShellExecute = true, CreateNoWindow = true } }; process.Start(); process.WaitForExit(); } private static bool RuleExists(string port) { var process = new Process { StartInfo = new ProcessStartInfo { FileName = "netsh", Arguments = $"advfirewall firewall show rule name=\"{FirewallRuleName}\"", UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true } }; process.Start(); var output = process.StandardOutput.ReadToEnd(); process.WaitForExit(); return output.Contains(port); } public string GetListeningAddress() { if (_listener.Prefixes.Count == 1 && _listener.Prefixes.First().Contains("localhost")) { return $"http://localhost:{_port}"; } return $"http://{GetLocalIPAddress()}:{_port}"; } public static string GetLocalIPAddress() { try { var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces() .Where(n => n.OperationalStatus == OperationalStatus.Up && n.NetworkInterfaceType != NetworkInterfaceType.Loopback); foreach (var network in networkInterfaces) { var properties = network.GetIPProperties(); var ipv4 = properties.UnicastAddresses .FirstOrDefault(ua => ua.Address.AddressFamily == AddressFamily.InterNetwork); if (ipv4 != null) { return ipv4.Address.ToString(); } } return "127.0.0.1"; } catch { return "127.0.0.1"; } } public void Start() { if (_isRunning) return; try { if (!IsAdministrator()) { _listener.Start(); _isRunning = true; Task.Run(HandleRequests); return; } AddFirewallRule(_port); _listener.Start(); _isRunning = true; Task.Run(HandleRequests); } catch (HttpListenerException ex) { if (IsAdministrator()) { RestartAsAdmin(); } else { Debug.WriteLine($"Failed to start HTTP server: {ex.Message}"); } } } public void Stop() { if (!_isRunning) return; _listener.Stop(); _isRunning = false; } public void UpdatePort(string newPort) { if (_port == newPort) return; bool wasRunning = _isRunning; if (wasRunning) Stop(); _port = newPort; UpdateListenerPrefixes(); if (wasRunning) Start(); } private async Task HandleRequests() { while (_isRunning) { try { var context = await _listener.GetContextAsync(); var path = context.Request.Url?.LocalPath ?? string.Empty; var handler = _routes .Where(r => path.StartsWith(r.Key)) .OrderByDescending(r => r.Key.Length) .Select(r => r.Value) .FirstOrDefault(); if (handler != null) { await handler.Invoke(context); } else { context.Response.StatusCode = 404; context.Response.Close(); } } catch (Exception ex) { Debug.WriteLine($"Error handling request: {ex.Message}"); _isRunning = false; } } } public bool IsRunning => _isRunning; public void Dispose() { if (_disposed) return; if (_isRunning) Stop(); _listener.Close(); _disposed = true; } } } ================================================ FILE: XAU/Services/HttpServer/ProfileRoutes.cs ================================================ using Newtonsoft.Json; using System.Net; using System.Text; public static class ProfileRoutes { public static Dictionary> GetRoutes(Func getXboxRestAPI, Func getXUIDOnly) { return new Dictionary> { { "/api/profile/me", async context => await ProfileMeRequest(context, getXboxRestAPI, getXUIDOnly) }, { "/api/profile/xuid/", async context => await ProfileXuidRequest(context, getXboxRestAPI) }, { "/api/profile/gt/", async context => await ProfileGamertagRequest(context, getXboxRestAPI) } }; } private static async Task ProfileMeRequest(HttpListenerContext context, Func getXboxRestAPI, Func getXUIDOnly) { var response = context.Response; try { string xuid = getXUIDOnly?.Invoke(); if (string.IsNullOrWhiteSpace(xuid)) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No XUID found" }); return; } var xboxRestAPI = getXboxRestAPI(); var profile = await xboxRestAPI.GetProfileAsync(xuid); if (profile == null) { response.StatusCode = 404; await SendJsonResponse(response, new { error = "Profile not found" }); return; } await SendJsonResponse(response, profile); } catch (Exception ex) { response.StatusCode = 500; await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message }); } } private static async Task ProfileXuidRequest(HttpListenerContext context, Func getXboxRestAPI) { var request = context.Request; var response = context.Response; try { if (request.Url.Segments.Length < 4) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No XUID provided in URL" }); return; } string xuid = request.Url.Segments.Last().TrimEnd('/'); if (string.IsNullOrWhiteSpace(xuid)) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "Invalid XUID in URL" }); return; } var xboxRestAPI = getXboxRestAPI(); var profile = await xboxRestAPI.GetProfileAsync(xuid); if (profile == null) { response.StatusCode = 404; await SendJsonResponse(response, new { error = "Profile not found" }); return; } await SendJsonResponse(response, profile); } catch (Exception ex) { response.StatusCode = 500; await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message }); } } private static async Task ProfileGamertagRequest(HttpListenerContext context, Func getXboxRestAPI) { var request = context.Request; var response = context.Response; try { if (request.Url.Segments.Length < 4) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No Gamertag provided in URL" }); return; } string gamertag = request.Url.Segments.Last().TrimEnd('/'); var xboxRestAPI = getXboxRestAPI(); // First, get the profile to extract XUID var gamertagProfile = await xboxRestAPI.GetGamertagProfileAsync(gamertag); if (gamertagProfile == null || gamertagProfile["profileUsers"] == null || !gamertagProfile["profileUsers"].Any()) { response.StatusCode = 404; await SendJsonResponse(response, new { error = "Gamertag not found" }); return; } string xuid = gamertagProfile["profileUsers"][0]["id"].ToString(); var fullProfile = await xboxRestAPI.GetProfileAsync(xuid); if (fullProfile == null) { response.StatusCode = 404; await SendJsonResponse(response, new { error = "Full profile not found" }); return; } await SendJsonResponse(response, fullProfile); } catch (Exception ex) { response.StatusCode = 500; await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message }); } } private static async Task SendJsonResponse(HttpListenerResponse response, object data) { var json = JsonConvert.SerializeObject(data); var buffer = Encoding.UTF8.GetBytes(json); response.ContentLength64 = buffer.Length; response.ContentType = "application/json"; await response.OutputStream.WriteAsync(buffer); response.Close(); } } ================================================ FILE: XAU/Services/HttpServer/Routes.cs ================================================ using Newtonsoft.Json; using System.Net; using System.Text; public static class Routes { public static Dictionary> GetRoutes(Func getXauthToken, Func getXboxRestAPI, Func getXUIDOnly) { var routes = new Dictionary> { { "/api/xauth", async context => await ApiXauthRequest(context, getXauthToken) } }; var profileRoutes = ProfileRoutes.GetRoutes(getXboxRestAPI, getXUIDOnly); foreach (var route in profileRoutes) { routes[route.Key] = route.Value; } var gameRoutes = GameRoutes.GetRoutes(getXboxRestAPI, getXUIDOnly); foreach (var route in gameRoutes) { routes[route.Key] = route.Value; } var achievementRoutes = AchievementRoutes.GetRoutes(getXboxRestAPI, getXUIDOnly); foreach (var route in achievementRoutes) { routes[route.Key] = route.Value; } var spoofingRoutes = SpoofingRoutes.GetRoutes(getXboxRestAPI, getXUIDOnly); foreach (var route in spoofingRoutes) { routes[route.Key] = route.Value; } var endpointRoutes = EndpointRoutes.GetRoutes(); foreach (var route in endpointRoutes) { routes[route.Key] = route.Value; } return routes; } private static async Task ApiXauthRequest(HttpListenerContext context, Func getXauthToken) { var response = context.Response; string query = context.Request.Url?.Query ?? string.Empty; bool isJson = query.Contains("?format=json", StringComparison.OrdinalIgnoreCase); var xauth = getXauthToken(); if (isJson) { var jsonResponse = new { token = xauth }; var json = JsonConvert.SerializeObject(jsonResponse); var buffer = Encoding.UTF8.GetBytes(json); response.ContentLength64 = buffer.Length; response.ContentType = "application/json"; await response.OutputStream.WriteAsync(buffer); } else { var buffer = Encoding.UTF8.GetBytes(xauth); response.ContentLength64 = buffer.Length; response.ContentType = "text/plain"; await response.OutputStream.WriteAsync(buffer); } response.Close(); } } ================================================ FILE: XAU/Services/HttpServer/SpoofingRoutes.cs ================================================ using Newtonsoft.Json; using System.IO; using System.Net; using System.Text; public static class SpoofingRoutes { public static Dictionary> GetRoutes(Func getXboxRestAPI, Func getXUIDOnly) { return new Dictionary> { { "/api/spoof", async context => await StartSpoofingRequest(context, getXboxRestAPI, getXUIDOnly) }, }; } private static async Task StartSpoofingRequest(HttpListenerContext context, Func getXboxRestAPI, Func getXUIDOnly) { var request = context.Request; var response = context.Response; try { if (request.HttpMethod != "GET") { response.StatusCode = 405; await SendJsonResponse(response, new { error = "Method not allowed. Use GET." }); return; } if (request.Url.Segments.Length < 3) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No Title ID provided in URL." }); return; } string titleId = request.Url.Segments.Last().TrimEnd('/'); if (string.IsNullOrWhiteSpace(titleId)) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "Invalid Title ID." }); return; } string xuid = getXUIDOnly?.Invoke(); if (string.IsNullOrWhiteSpace(xuid)) { response.StatusCode = 400; await SendJsonResponse(response, new { error = "No XUID found." }); return; } var xboxRestAPI = getXboxRestAPI(); await xboxRestAPI.SendHeartbeatAsync(xuid, titleId); response.StatusCode = 200; await SendJsonResponse(response, new { message = "Spoofing started successfully.", titleId }); } catch (Exception ex) { response.StatusCode = 500; await SendJsonResponse(response, new { error = ex.Message, innerError = ex.InnerException?.Message }); } } private static async Task SendJsonResponse(HttpListenerResponse response, object data) { var json = JsonConvert.SerializeObject(data); var buffer = Encoding.UTF8.GetBytes(json); response.ContentLength64 = buffer.Length; response.ContentType = "application/json"; await response.OutputStream.WriteAsync(buffer); response.Close(); } } public class SpoofingRequest { public string TitleId { get; set; } } ================================================ FILE: XAU/Theme/ThemeConstants.xaml ================================================ 15 ================================================ FILE: XAU/Usings.cs ================================================ global using CommunityToolkit.Mvvm.ComponentModel; global using CommunityToolkit.Mvvm.Input; global using System; global using System.Windows; global using Wpf.Ui; //fix these to override winforms imports global using Clipboard = System.Windows.Clipboard; global using Brush = System.Windows.Media.Brush; global using HtmlDocument = HtmlAgilityPack.HtmlDocument; global using KeyEventArgs = System.Windows.Input.KeyEventArgs; global using ButtonBase = System.Windows.Controls.Primitives.ButtonBase; global using ListBox = System.Windows.Controls.ListBox; ================================================ FILE: XAU/Util/Constants/Constants.cs ================================================ // Minimize total number of string allocations if .NET runtime is opting to not intern them struct StringConstants { public const string Gamerscore = @"Gamerscore"; public const string Achieved = @"Achieved"; public const string ZeroUid = @"00000000-0000-0000-0000-000000000000"; } struct HeaderNames { public const string ContractVersion = @"x-xbl-contract-version"; public const string AcceptEncoding = @"Accept-Encoding"; public const string Accept = @"accept"; public const string Authorization = @"Authorization"; public const string AcceptLanguage = @"accept-language"; public const string Host = @"Host"; public const string Connection = @"Connection"; public const string Signature = @"Signature"; } struct HeaderValues { public const string ContractVersion2 = @"2"; public const string ContractVersion3 = @"3"; public const string ContractVersion4 = @"4"; public const string ContractVersion5 = @"5"; public const string AcceptEncoding = @"gzip, deflate"; public const string Accept = @"application/json"; public const string KeepAlive = @"Keep-Alive"; public const string Signature = @"RGFtbklHb3R0YU1ha2VUaGlzU3RyaW5nU3VwZXJMb25nSHVoLkRvbnRFdmVuS25vd1doYXRTaG91bGRCZUhlcmVEcmFmZlN0cmluZw=="; } struct OpenableLinks { // Hardcoded links to socials public const string Discord = @"https://discord.gg/fCqM7287jG"; public const string GitHubUserUrl = @"https://github.com/ItsLogic"; public const string EventsDocumentationUrl = @"https://github.com/Fumo-Unlockers/Xbox-Achievement-Unlocker/blob/Main/Doc/Events.md"; } struct Hosts { // Xbox Live Header Host Values public const string Achievements = @"achievements.xboxlive.com"; public const string Profile = @"profile.xboxlive.com"; public const string PeopleHub = @"peoplehub.xboxlive.com"; public const string TitleHub = @"titlehub.xboxlive.com"; public const string Telemetry = @"v20.events.data.microsoft.com"; public const string GitHubApi = @"api.github.com"; public const string GitHubRaw = @"raw.githubusercontent.com"; } public struct BasicXboxAPIUris { public const string GamertagUrl = @"https://profile.xboxlive.com/users/me/profile/settings?settings=Gamertag"; public const string WatermarksUrl = @"https://dlassets-ssl.xboxlive.com/public/content/ppl/watermarks/"; public const string GamepassCatalogUrl = @"https://catalog.gamepass.com/products?market=GB&language=en-GB&hydration=PCHome"; public const string TelemetryUrl = @"https://v20.events.data.microsoft.com/OneCollector/1.0/"; public const string UserStatsUrl = @"https://userstats.xboxlive.com/batch"; } public struct InterpolatedXboxAPIUrls { // TODO: could uri build things public const string GamepassMembershipUrl = "https://xgrant.xboxlive.com/users/xuid({0})/programInfo?filter=profile,activities,catalog"; public const string ProfileUrl = "https://peoplehub.xboxlive.com/users/me/people/xuids({0})/decoration/detail,preferredColor,presenceDetail,multiplayerSummary"; public const string TitleUrl = "https://titlehub.xboxlive.com/users/xuid({0})/titles/batch/decoration/GamePass,Achievement,Stats"; public const string TitlesUrl = "https://titlehub.xboxlive.com/users/xuid({0})/titles/titleHistory/decoration/Achievement,detail,scid?maxItems=10000"; public const string QueryAchievementsUrl = "https://achievements.xboxlive.com/users/xuid({0})/achievements?titleId={1}&maxItems=1000"; public const string QueryAchievements360Url = "https://achievements.xboxlive.com/users/xuid({0})/titleachievements?titleId={1}&maxItems=1000"; public const string UpdateAchievementsUrl = "https://achievements.xboxlive.com/users/xuid({0})/achievements/{1}/update"; public const string HeartbeatUrl = "https://presence-heartbeat.xboxlive.com/users/xuid({0})/devices/current/"; public const string GamertagSearch = "https://profile.xboxlive.com/users/gt({0})/profile/settings?settings=GameDisplayPicRaw,Gamerscore,Gamertag"; } public struct ProcessNames { public const string XboxPcApp = @"XboxPcApp"; public const string Solitaire = @"Solitaire"; } public struct AppLaunchUris { public const string Solitaire = @"shell:appsFolder\Microsoft.MicrosoftSolitaireCollection_8wekyb3d8bbwe!App"; } public struct EventsUrls { public const string Zip = @"https://github.com/Fumo-Unlockers/Xbox-Achievement-Unlocker/raw/Events-Data/Events.zip"; public const string MetaUrl = @"https://raw.githubusercontent.com/Fumo-Unlockers/Xbox-Achievement-Unlocker/Events-Data/meta.json"; } ================================================ FILE: XAU/Util/Etw/EtwTokenCapture.cs ================================================ using System.Diagnostics; using System.IO; using System.Text; using System.Text.RegularExpressions; using XAU.ViewModels.Pages; namespace XAU.Util.Etw { static class EtwTokenCapture { private static readonly string EtwSessionName = "XAU_EventsTokenCapture"; private static readonly string EtwTempDir = Path.Combine(Path.GetTempPath(), "XAU_ETW"); private static readonly string EtwEtlPath = Path.Combine(EtwTempDir, "capture.etl"); private static readonly Regex TicketHeaderRegex = new Regex( @"""(\d{5,12})""\s*=\s*""(x:XBL3\.0 x=[^""]{100,})""", RegexOptions.Compiled); private static readonly Regex BareTokenRegex = new Regex( @"x:XBL3\.0 x=[\w;+/=\-\.]{100,}", RegexOptions.Compiled); private static readonly Regex OneCollectorUrlRegex = new Regex( @"v20\.events\.data\.microsoft\.com|OneCollector", RegexOptions.Compiled); // Events RP x5t — used to distinguish events tokens from XAUTH tokens. // This is the certificate thumbprint for events.xboxlive.com; it appears // in the decoded JWE header JSON as "x5t":"9wLGzMJDNz..." private const string EventsRpX5t = "9wLGzMJDNz"; /// /// Checks whether a token was encrypted for the events RP by decoding /// the JWE header and verifying the x5t certificate thumbprint. /// Token format: "x:XBL3.0 x={hash};{JWE}" or "XBL3.0 x={hash};{JWE}" /// public static bool IsEventsRpToken(string token) { try { int semiIdx = token.IndexOf(';'); if (semiIdx < 0) return false; string jwe = token.Substring(semiIdx + 1); int dotIdx = jwe.IndexOf('.'); if (dotIdx <= 0) return false; string headerB64 = jwe.Substring(0, dotIdx); // Base64url → standard Base64 string padded = headerB64.Replace('-', '+').Replace('_', '/'); switch (padded.Length % 4) { case 2: padded += "=="; break; case 3: padded += "="; break; } string headerJson = Encoding.UTF8.GetString(Convert.FromBase64String(padded)); return headerJson.Contains(EventsRpX5t); } catch { return false; } } /// /// One-shot: start trace, wait, stop, extract, cleanup. Returns token or null. /// public static string Capture(int captureSeconds) { HomeViewModel.EventsLog($"Starting ETW capture for {captureSeconds}s..."); Cleanup(); string method = Start(); if (method == null) { HomeViewModel.EventsLog("Failed to start ETW trace (not running as admin?)"); return null; } HomeViewModel.EventsLog($"ETW started via {method}"); Thread.Sleep(captureSeconds * 1000); Stop(method); // Give it a moment to flush Thread.Sleep(2000); string token = ExtractTokens(); CleanupFiles(); return token; } public static void Cleanup() { try { RunShellCommand("netsh", "trace stop", 15000); } catch { } try { RunShellCommand("logman", $"stop {EtwSessionName} -ets", 10000); } catch { } } public static string Start() { Directory.CreateDirectory(EtwTempDir); // Try netsh trace (captures most HTTP traffic including WinHTTP) var (code, stdout, stderr) = RunShellCommand("netsh", $"trace start scenario=InternetClient_dbg capture=no tracefile=\"{EtwEtlPath}\" maxsize=256 overwrite=yes report=disabled", 15000); HomeViewModel.EventsLog($"netsh trace start: exit={code}, stdout={stdout.Trim()}, stderr={stderr.Trim()}"); if (code == 0) return "netsh"; // Try logman with WinHttp provider (code, stdout, stderr) = RunShellCommand("logman", $"start {EtwSessionName} -p Microsoft-Windows-WinHttp 0xFFFFFFFF 0xFF -o \"{EtwEtlPath}\" -ets", 10000); HomeViewModel.EventsLog($"logman WinHttp: exit={code}, stdout={stdout.Trim()}, stderr={stderr.Trim()}"); if (code == 0) return "logman-winhttp"; // Try logman with WinINet provider (code, stdout, stderr) = RunShellCommand("logman", $"start {EtwSessionName} -p Microsoft-Windows-WinINet 0xFFFFFFFF 0xFF -o \"{EtwEtlPath}\" -ets", 10000); HomeViewModel.EventsLog($"logman WinINet: exit={code}, stdout={stdout.Trim()}, stderr={stderr.Trim()}"); if (code == 0) return "logman-wininet"; HomeViewModel.EventsLog("All ETW start methods failed"); return null; } public static void Stop(string method) { if (method == "netsh") { var (code, _, _) = RunShellCommand("netsh", "trace stop", 30000); HomeViewModel.EventsLog($"netsh trace stop: exit={code}"); } else if (method != null) { var (code, _, _) = RunShellCommand("logman", $"stop {EtwSessionName} -ets", 15000); HomeViewModel.EventsLog($"logman stop: exit={code}"); } } public static string ExtractTokens() { if (!File.Exists(EtwEtlPath)) { HomeViewModel.EventsLog("ETL file not found"); return null; } var fileSize = new FileInfo(EtwEtlPath).Length; HomeViewModel.EventsLog($"ETL file size: {fileSize / 1024}KB"); const int chunkSize = 64 * 1024 * 1024; // 64MB const int overlap = 8 * 1024; // 8KB overlap var candidates = new List<(string token, int score)>(); int totalXblHits = 0; using (var fs = new FileStream(EtwEtlPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { byte[] buffer = new byte[chunkSize + overlap]; long position = 0; int chunkNum = 0; while (position < fs.Length) { fs.Position = position; int bytesRead = fs.Read(buffer, 0, buffer.Length); if (bytesRead == 0) break; // Work directly from buffer (avoid extra copy) string ascii = Encoding.ASCII.GetString(buffer, 0, bytesRead); // Diagnostic: count raw "XBL3.0" occurrences int xblCount = 0; int searchIdx = 0; while ((searchIdx = ascii.IndexOf("XBL3.0", searchIdx, StringComparison.Ordinal)) >= 0) { xblCount++; searchIdx += 6; } totalXblHits += xblCount; HomeViewModel.EventsLog($"Chunk {chunkNum}: {bytesRead / 1024}KB, XBL3.0 hits={xblCount}, regex searching..."); SearchForTokens(ascii, candidates); // UTF-16 → strip null bytes to get ASCII string stripped = StripNullBytes(buffer, bytesRead); SearchForTokens(stripped, candidates); HomeViewModel.EventsLog($"Chunk {chunkNum}: candidates so far={candidates.Count}"); position += chunkSize; chunkNum++; } } HomeViewModel.EventsLog($"Total XBL3.0 hits across all chunks: {totalXblHits}"); if (candidates.Count == 0) { HomeViewModel.EventsLog($"No regex candidates found (XBL3.0 hits={totalXblHits}). Trying IndexOf fallback..."); // Fallback: re-read and use IndexOf to extract tokens directly if (totalXblHits > 0) { using var fs2 = new FileStream(EtwEtlPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); byte[] allBytes = new byte[fs2.Length]; fs2.Read(allBytes, 0, allBytes.Length); string stripped = StripNullBytes(allBytes, allBytes.Length); string marker = "x:XBL3.0 x="; int idx = 0; int found = 0; while ((idx = stripped.IndexOf(marker, idx, StringComparison.Ordinal)) >= 0) { // Extract up to 4000 chars from this position int maxLen = Math.Min(4000, stripped.Length - idx); string raw = stripped.Substring(idx, maxLen); string token = CleanToken(raw); if (token != null) { int score = ScoreCandidate(stripped, idx, token, null); candidates.Add((token, score)); found++; HomeViewModel.EventsLog($"IndexOf fallback found token: len={token.Length}, score={score}"); } else { // Log why CleanToken rejected it int endSnip = Math.Min(80, raw.Length); HomeViewModel.EventsLog($"IndexOf hit rejected by CleanToken at pos={idx}, start: {raw.Substring(0, endSnip)}"); } idx += marker.Length; } HomeViewModel.EventsLog($"IndexOf fallback: {found} tokens from {totalXblHits} XBL3.0 hits"); } if (candidates.Count == 0) { HomeViewModel.EventsLog("No token candidates found in ETL"); return null; } } // Sort by score descending candidates.Sort((a, b) => b.score.CompareTo(a.score)); HomeViewModel.EventsLog($"Found {candidates.Count} candidate(s):"); foreach (var (token, score) in candidates.Take(5)) { int semi = token.IndexOf(';'); string hash = semi > 0 ? token.Substring(token.IndexOf("x=") + 2, semi - token.IndexOf("x=") - 2) : "?"; HomeViewModel.EventsLog($" score={score}, len={token.Length}, hash={hash}"); } // Validate the best candidates foreach (var (token, score) in candidates) { if (IsEventsRpToken(token)) { HomeViewModel.EventsLog($"Candidate validated (x5t check passed), score={score}, len={token.Length}"); return token; } } HomeViewModel.EventsLog("No candidate passed x5t validation"); return null; } public static void CleanupFiles() { try { if (Directory.Exists(EtwTempDir)) { foreach (var file in Directory.GetFiles(EtwTempDir)) { try { File.Delete(file); } catch { } } try { Directory.Delete(EtwTempDir, true); } catch { } } } catch (Exception ex) { HomeViewModel.EventsLog($"Cleanup error: {ex.Message}"); } } private static (int exitCode, string stdout, string stderr) RunShellCommand(string fileName, string args, int timeoutMs = 30000) { try { var psi = new ProcessStartInfo { FileName = fileName, Arguments = args, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, }; using var proc = Process.Start(psi); string stdout = proc.StandardOutput.ReadToEnd(); string stderr = proc.StandardError.ReadToEnd(); proc.WaitForExit(timeoutMs); return (proc.ExitCode, stdout, stderr); } catch (Exception ex) { return (-1, "", ex.Message); } } private static void SearchForTokens(string text, List<(string token, int score)> candidates) { // Search with ticket header pattern (has title ID context) foreach (Match match in TicketHeaderRegex.Matches(text)) { string titleId = match.Groups[1].Value; string token = CleanToken(match.Groups[2].Value); if (token == null) continue; int score = ScoreCandidate(text, match.Index, token, titleId); candidates.Add((token, score)); } // Search with bare token pattern foreach (Match match in BareTokenRegex.Matches(text)) { string token = CleanToken(match.Value); if (token == null) continue; // Skip if already found via ticket header if (candidates.Any(c => c.token == token)) continue; int score = ScoreCandidate(text, match.Index, token, null); candidates.Add((token, score)); } } private static string CleanToken(string raw) { if (string.IsNullOrEmpty(raw)) return null; // Trim at first non-token character int end = raw.Length; for (int i = 0; i < raw.Length; i++) { char c = raw[i]; if (c < 0x20 || c > 0x7E || c == '"' || c == '\'' || c == '<' || c == '>' || c == '{' || c == '}') { end = i; break; } } string token = raw.Substring(0, end).TrimEnd(); if (!token.StartsWith("x:XBL3.0 x=")) return null; if (token.Length < 100) return null; if (!token.Contains(';')) return null; return token; } private static int ScoreCandidate(string text, int matchIndex, string token, string titleId) { int score = 0; // OneCollector URL proximity (+5000) int searchStart = Math.Max(0, matchIndex - 2000); int searchLen = Math.Min(4000, text.Length - searchStart); string vicinity = text.Substring(searchStart, searchLen); if (OneCollectorUrlRegex.IsMatch(vicinity)) score += 5000; // Has ticket ID context (+2000) if (!string.IsNullOrEmpty(titleId)) score += 2000; // Proper x:XBL3.0 prefix (+1000) if (token.StartsWith("x:XBL3.0 x=")) score += 1000; // Length bonus (longer tokens are more likely complete) score += token.Length / 10; return score; } private static string StripNullBytes(byte[] data, int length) { var sb = new StringBuilder(length / 2); for (int i = 0; i < length; i++) { if (data[i] != 0 && data[i] >= 0x20 && data[i] <= 0x7E) sb.Append((char)data[i]); } return sb.ToString(); } } } ================================================ FILE: XAU/Util/Files/FileDownloader.cs ================================================ using System.ComponentModel; using System.IO; using System.Net.Http; public class FileDownloader : IDisposable { private readonly HttpClient httpClient; public FileDownloader() { httpClient = new HttpClient(); } public void Dispose() { httpClient.Dispose(); } public async Task DownloadFileAsync(string url, string destinationFilePath, Action? updateToolCallback = null) { try { HttpResponseMessage response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); using (Stream contentStream = await response.Content.ReadAsStreamAsync()) { using (FileStream fileStream = new FileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true)) { await contentStream.CopyToAsync(fileStream); } } // Invoke the provided callback method upon successful download updateToolCallback?.Invoke(this, new AsyncCompletedEventArgs(null, false, null)); } catch (Exception ex) { // Handle exceptions Console.WriteLine($"Error downloading file: {ex.Message}"); } } } ================================================ FILE: XAU/Util/Memory/Methods/AoB.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using static Memory.Imps; namespace Memory { public partial class Mem { /// /// Array of byte scan. /// /// array of bytes to search for, OR your ini code label. /// Include writable addresses in scan /// Include executable addresses in scan /// ini file (OPTIONAL) /// IEnumerable of all addresses found. public Task> AoBScan(string search, bool writable = false, bool executable = true, string file = "") { return AoBScan(0, long.MaxValue, search, writable, executable, false, file); } /// /// Array of Byte scan. /// /// Your starting address. /// ending address /// array of bytes to search for, OR your ini code label. /// ini file (OPTIONAL) /// Include writable addresses in scan /// Include executable addresses in scan /// Include mapped addresses in scan /// IEnumerable of all addresses found. public Task> AoBScan(long start, long end, string search, bool writable = false, bool executable = true, bool mapped = false, string file = "") { // Not including read only memory was scan behavior prior. return AoBScan(start, end, search, false, writable, executable, mapped, file); } /// /// Array of Byte scan. /// /// Your starting address. /// ending address /// array of bytes to search for, OR your ini code label. /// ini file (OPTIONAL) /// Include readable addresses in scan /// Include writable addresses in scan /// Include executable addresses in scan /// Include mapped addresses in scan /// IEnumerable of all addresses found. public Task> AoBScan(long start, long end, string search, bool readable, bool writable, bool executable, bool mapped, string file = "") { return Task.Run(() => { var memRegionList = new List(); string memCode = LoadCode(search, file); string[] stringByteArray = memCode.Split(' '); byte[] aobPattern = new byte[stringByteArray.Length]; byte[] mask = new byte[stringByteArray.Length]; for (var i = 0; i < stringByteArray.Length; i++) { string ba = stringByteArray[i]; if (ba == "??" || (ba.Length == 1 && ba == "?")) { mask[i] = 0x00; stringByteArray[i] = "0x00"; } else if (Char.IsLetterOrDigit(ba[0]) && ba[1] == '?') { mask[i] = 0xF0; stringByteArray[i] = ba[0] + "0"; } else if (Char.IsLetterOrDigit(ba[1]) && ba[0] == '?') { mask[i] = 0x0F; stringByteArray[i] = "0" + ba[1]; } else mask[i] = 0xFF; } for (int i = 0; i < stringByteArray.Length; i++) aobPattern[i] = (byte)(Convert.ToByte(stringByteArray[i], 16) & mask[i]); SYSTEM_INFO sys_info = new SYSTEM_INFO(); GetSystemInfo(out sys_info); UIntPtr proc_min_address = sys_info.minimumApplicationAddress; UIntPtr proc_max_address = sys_info.maximumApplicationAddress; if (start < (long)proc_min_address.ToUInt64()) start = (long)proc_min_address.ToUInt64(); if (end > (long)proc_max_address.ToUInt64()) end = (long)proc_max_address.ToUInt64(); Debug.WriteLine("[DEBUG] memory scan starting... (start:0x" + start.ToString(MSize()) + " end:0x" + end.ToString(MSize()) + " time:" + DateTime.Now.ToString("h:mm:ss tt") + ")"); UIntPtr currentBaseAddress = new UIntPtr((ulong)start); MEMORY_BASIC_INFORMATION memInfo = new MEMORY_BASIC_INFORMATION(); //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()); while (VirtualQueryEx(mProc.Handle, currentBaseAddress, out memInfo).ToUInt64() != 0 && currentBaseAddress.ToUInt64() < (ulong)end && currentBaseAddress.ToUInt64() + (ulong)memInfo.RegionSize > currentBaseAddress.ToUInt64()) { bool isValid = memInfo.State == MEM_COMMIT; isValid &= memInfo.BaseAddress.ToUInt64() < (ulong)proc_max_address.ToUInt64(); isValid &= ((memInfo.Protect & PAGE_GUARD) == 0); isValid &= ((memInfo.Protect & PAGE_NOACCESS) == 0); isValid &= (memInfo.Type == MEM_PRIVATE) || (memInfo.Type == MEM_IMAGE); if (mapped) isValid &= (memInfo.Type == MEM_MAPPED); if (isValid) { bool isReadable = (memInfo.Protect & PAGE_READONLY) > 0; bool isWritable = ((memInfo.Protect & PAGE_READWRITE) > 0) || ((memInfo.Protect & PAGE_WRITECOPY) > 0) || ((memInfo.Protect & PAGE_EXECUTE_READWRITE) > 0) || ((memInfo.Protect & PAGE_EXECUTE_WRITECOPY) > 0); bool isExecutable = ((memInfo.Protect & PAGE_EXECUTE) > 0) || ((memInfo.Protect & PAGE_EXECUTE_READ) > 0) || ((memInfo.Protect & PAGE_EXECUTE_READWRITE) > 0) || ((memInfo.Protect & PAGE_EXECUTE_WRITECOPY) > 0); isReadable &= readable; isWritable &= writable; isExecutable &= executable; isValid &= isReadable || isWritable || isExecutable; } if (!isValid) { currentBaseAddress = new UIntPtr(memInfo.BaseAddress.ToUInt64() + (ulong)memInfo.RegionSize); continue; } MemoryRegionResult memRegion = new MemoryRegionResult { CurrentBaseAddress = currentBaseAddress, RegionSize = memInfo.RegionSize, RegionBase = memInfo.BaseAddress }; currentBaseAddress = new UIntPtr(memInfo.BaseAddress.ToUInt64() + (ulong)memInfo.RegionSize); //Console.WriteLine("SCAN start:" + memRegion.RegionBase.ToString() + " end:" + currentBaseAddress.ToString()); if (memRegionList.Count > 0) { var previousRegion = memRegionList[memRegionList.Count - 1]; if ((long)previousRegion.RegionBase + previousRegion.RegionSize == (long)memInfo.BaseAddress) { memRegionList[memRegionList.Count - 1] = new MemoryRegionResult { CurrentBaseAddress = previousRegion.CurrentBaseAddress, RegionBase = previousRegion.RegionBase, RegionSize = previousRegion.RegionSize + memInfo.RegionSize }; continue; } } memRegionList.Add(memRegion); } ConcurrentBag bagResult = new ConcurrentBag(); Parallel.ForEach(memRegionList, (item, parallelLoopState, index) => { long[] compareResults = CompareScan(item, aobPattern, mask); foreach (long result in compareResults) bagResult.Add(result); }); Debug.WriteLine("[DEBUG] memory scan completed. (time:" + DateTime.Now.ToString("h:mm:ss tt") + ")"); return bagResult.ToList().OrderBy(c => c).AsEnumerable(); }); } private long[] CompareScan(MemoryRegionResult item, byte[] aobPattern, byte[] mask) { if (mask.Length != aobPattern.Length) throw new ArgumentException($"{nameof(aobPattern)}.Length != {nameof(mask)}.Length"); IntPtr buffer = Marshal.AllocHGlobal((int)item.RegionSize); ReadProcessMemory(mProc.Handle, item.CurrentBaseAddress, buffer, (UIntPtr)item.RegionSize, out ulong bytesRead); int result = 0 - aobPattern.Length; List ret = new List(); unsafe { do { result = FindPattern((byte*)buffer.ToPointer(), (int)bytesRead, aobPattern, mask, result + aobPattern.Length); if (result >= 0) ret.Add((long)item.CurrentBaseAddress + result); } while (result != -1); } Marshal.FreeHGlobal(buffer); return ret.ToArray(); } private unsafe int FindPattern(byte* body, int bodyLength, byte[] pattern, byte[] masks, int start = 0) { int foundIndex = -1; if (bodyLength <= 0 || pattern.Length <= 0 || start > bodyLength - pattern.Length || pattern.Length > bodyLength) return foundIndex; for (int index = start; index <= bodyLength - pattern.Length; index++) { if (((body[index] & masks[0]) == (pattern[0] & masks[0]))) { var match = true; for (int index2 = pattern.Length - 1; index2 >= 1; index2--) { if ((body[index + index2] & masks[index2]) == (pattern[index2] & masks[index2])) continue; match = false; break; } if (!match) continue; foundIndex = index; break; } } return foundIndex; } } } ================================================ FILE: XAU/Util/Memory/Methods/Read.cs ================================================ using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Text; using System.Threading; using System.Threading.Tasks; using static Memory.Imps; namespace Memory { public partial class Mem { /// /// Read a string value from an address. /// /// address, module + pointer + offset, module + offset OR label in .ini file. /// path and name of ini file. (OPTIONAL) /// length of bytes to read (OPTIONAL) /// terminate string at null char /// System.Text.Encoding.UTF8 (DEFAULT). Other options: ascii, unicode, utf32, utf7 /// public string ReadString(string code, string file = "", int length = 32, bool zeroTerminated = true, System.Text.Encoding stringEncoding = null) { if (stringEncoding == null) stringEncoding = System.Text.Encoding.UTF8; byte[] memoryNormal = new byte[length]; UIntPtr theCode = GetCode(code, file); if (theCode == null || theCode == UIntPtr.Zero || theCode.ToUInt64() < 0x10000) return ""; if (ReadProcessMemory(mProc.Handle, theCode, memoryNormal, (UIntPtr)length, IntPtr.Zero)) return (zeroTerminated) ? stringEncoding.GetString(memoryNormal).Split('\0')[0] : stringEncoding.GetString(memoryNormal); else return ""; } } } ================================================ FILE: XAU/Util/Memory/Structures/Imports.cs ================================================ using System; using System.Runtime.InteropServices; using System.Text; namespace Memory { public class Imps { [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess( UInt32 dwDesiredAccess, bool bInheritHandle, Int32 dwProcessId ); #if WINXP #else [DllImport("kernel32.dll", EntryPoint = "VirtualQueryEx")] public static extern UIntPtr Native_VirtualQueryEx(IntPtr hProcess, UIntPtr lpAddress, out MEMORY_BASIC_INFORMATION32 lpBuffer, UIntPtr dwLength); [DllImport("kernel32.dll", EntryPoint = "VirtualQueryEx")] public static extern UIntPtr Native_VirtualQueryEx(IntPtr hProcess, UIntPtr lpAddress, out MEMORY_BASIC_INFORMATION64 lpBuffer, UIntPtr dwLength); [DllImport("kernel32.dll")] public static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo); #endif [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] public static extern uint GetPrivateProfileString( string lpAppName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, uint nSize, string lpFileName); [DllImport("kernel32.dll")] public static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr lpBaseAddress, [Out] byte[] lpBuffer, UIntPtr nSize, IntPtr lpNumberOfBytesRead); [DllImport("kernel32.dll")] public static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr lpBaseAddress, [Out] IntPtr lpBuffer, UIntPtr nSize, out ulong lpNumberOfBytesRead); [DllImport("kernel32")] public static extern bool IsWow64Process(IntPtr hProcess, out bool lpSystemInfo); // used for memory allocation public const uint MEM_FREE = 0x10000; public const uint MEM_COMMIT = 0x00001000; public const uint MEM_RESERVE = 0x00002000; public const uint PAGE_READONLY = 0x02; public const uint PAGE_READWRITE = 0x04; public const uint PAGE_WRITECOPY = 0x08; public const uint PAGE_EXECUTE_READWRITE = 0x40; public const uint PAGE_EXECUTE_WRITECOPY = 0x80; public const uint PAGE_EXECUTE = 0x10; public const uint PAGE_EXECUTE_READ = 0x20; public const uint PAGE_GUARD = 0x100; public const uint PAGE_NOACCESS = 0x01; public const uint MEM_PRIVATE = 0x20000; public const uint MEM_IMAGE = 0x1000000; public const uint MEM_MAPPED = 0x40000; public struct SYSTEM_INFO { public ushort processorArchitecture; ushort reserved; public uint pageSize; public UIntPtr minimumApplicationAddress; public UIntPtr maximumApplicationAddress; public IntPtr activeProcessorMask; public uint numberOfProcessors; public uint processorType; public uint allocationGranularity; public ushort processorLevel; public ushort processorRevision; } public struct MEMORY_BASIC_INFORMATION32 { public UIntPtr BaseAddress; public UIntPtr AllocationBase; public uint AllocationProtect; public uint RegionSize; public uint State; public uint Protect; public uint Type; } public struct MEMORY_BASIC_INFORMATION64 { public UIntPtr BaseAddress; public UIntPtr AllocationBase; public uint AllocationProtect; public uint __alignment1; public ulong RegionSize; public uint State; public uint Protect; public uint Type; public uint __alignment2; } public struct MEMORY_BASIC_INFORMATION { public UIntPtr BaseAddress; public UIntPtr AllocationBase; public uint AllocationProtect; public long RegionSize; public uint State; public uint Protect; public uint Type; } } } ================================================ FILE: XAU/Util/Memory/Structures/MemoryRegionResult.cs ================================================ using System; namespace Memory { /// /// AoB scan information. /// struct MemoryRegionResult { public UIntPtr CurrentBaseAddress { get; set; } public long RegionSize { get; set; } public UIntPtr RegionBase { get; set; } } } ================================================ FILE: XAU/Util/Memory/Structures/Process.cs ================================================ using System; using System.Diagnostics; namespace Memory { /// /// Information about the opened process. /// public class Proc { public Process Process { get; set; } public IntPtr Handle { get; set; } public bool Is64Bit { get; set; } public ProcessModule MainModule { get; set; } } } ================================================ FILE: XAU/Util/Memory/memory.cs ================================================ using System; using System.IO; using System.IO.Pipes; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.Diagnostics; using System.Globalization; using System.Threading.Tasks; using System.ComponentModel; using static Memory.Imps; namespace Memory { /// /// Memory.dll class. Full documentation at https://github.com/erfg12/memory.dll/wiki /// public partial class Mem { public Proc mProc = new Proc(); public UIntPtr VirtualQueryEx(IntPtr hProcess, UIntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer) { UIntPtr retVal; // TODO: Need to change this to only check once. if (mProc.Is64Bit || IntPtr.Size == 8) { // 64 bit MEMORY_BASIC_INFORMATION64 tmp64 = new MEMORY_BASIC_INFORMATION64(); retVal = Native_VirtualQueryEx(hProcess, lpAddress, out tmp64, new UIntPtr((uint)Marshal.SizeOf(tmp64))); lpBuffer.BaseAddress = tmp64.BaseAddress; lpBuffer.AllocationBase = tmp64.AllocationBase; lpBuffer.AllocationProtect = tmp64.AllocationProtect; lpBuffer.RegionSize = (long)tmp64.RegionSize; lpBuffer.State = tmp64.State; lpBuffer.Protect = tmp64.Protect; lpBuffer.Type = tmp64.Type; return retVal; } MEMORY_BASIC_INFORMATION32 tmp32 = new MEMORY_BASIC_INFORMATION32(); retVal = Native_VirtualQueryEx(hProcess, lpAddress, out tmp32, new UIntPtr((uint)Marshal.SizeOf(tmp32))); lpBuffer.BaseAddress = tmp32.BaseAddress; lpBuffer.AllocationBase = tmp32.AllocationBase; lpBuffer.AllocationProtect = tmp32.AllocationProtect; lpBuffer.RegionSize = tmp32.RegionSize; lpBuffer.State = tmp32.State; lpBuffer.Protect = tmp32.Protect; lpBuffer.Type = tmp32.Type; return retVal; } /// /// Open the PC game process with all security and access rights. /// /// Use process name or process ID here. /// Process opened successfully or failed. /// Show reason open process fails public bool OpenProcess(int pid, out string FailReason) { /*if (!IsAdmin()) { Debug.WriteLine("WARNING: This program may not be running with raised privileges! Visit https://github.com/erfg12/memory.dll/wiki/Administrative-Privileges"); }*/ if (pid <= 0) { FailReason = "OpenProcess given proc ID 0."; Debug.WriteLine("ERROR: OpenProcess given proc ID 0."); return false; } if (mProc.Process != null && mProc.Process.Id == pid) { FailReason = "mProc.Process is null"; return true; } try { mProc.Process = Process.GetProcessById(pid); if (mProc.Process != null && !mProc.Process.Responding) { Debug.WriteLine("ERROR: OpenProcess: Process is not responding or null."); FailReason = "Process is not responding or null."; return false; } mProc.Handle = Imps.OpenProcess(0x1F0FFF, true, pid); if (mProc.Handle == IntPtr.Zero) { var eCode = Marshal.GetLastWin32Error(); Debug.WriteLine("ERROR: OpenProcess has failed opening a handle to the target process (GetLastWin32ErrorCode: " + eCode + ")"); Process.LeaveDebugMode(); mProc = null; FailReason = "failed opening a handle to the target process(GetLastWin32ErrorCode: " + eCode + ")"; return false; } // Lets set the process to 64bit or not here (cuts down on api calls) mProc.Is64Bit = Environment.Is64BitOperatingSystem && (IsWow64Process(mProc.Handle, out bool retVal) && !retVal); mProc.MainModule = mProc.Process.MainModule; //GetModules(); Debug.WriteLine("Process #" + mProc.Process + " is now open."); FailReason = ""; return true; } catch (Exception ex) { Debug.WriteLine("ERROR: OpenProcess has crashed. " + ex); FailReason = "OpenProcess has crashed. " + ex; return false; } } /// /// Open the PC game process with all security and access rights. /// /// Use process name or process ID here. /// public bool OpenProcess(string proc) { return OpenProcess(GetProcIdFromName(proc), out string FailReason); } /// /// Get the process ID number by process name. /// /// Example: "eqgame". Use task manager to find the name. Do not include .exe /// public int GetProcIdFromName(string name) //new 1.0.2 function { Process[] processlist = Process.GetProcesses(); if (name.ToLower().Contains(".exe")) name = name.Replace(".exe", ""); if (name.ToLower().Contains(".bin")) // test name = name.Replace(".bin", ""); foreach (System.Diagnostics.Process theprocess in processlist) { if (theprocess.ProcessName.Equals(name, StringComparison.CurrentCultureIgnoreCase)) //find (name).exe in the process list (use task manager to find the name) return theprocess.Id; } return 0; //if we fail to find it } /// /// Get code. If just the ini file name is given with no path, it will assume the file is next to the executable. /// /// label for address or code /// path and name of ini file /// public string LoadCode(string name, string iniFile) { StringBuilder returnCode = new StringBuilder(1024); uint read_ini_result; if (!String.IsNullOrEmpty(iniFile)) { if (File.Exists(iniFile)) { read_ini_result = GetPrivateProfileString("codes", name, "", returnCode, (uint)returnCode.Capacity, iniFile); //Debug.WriteLine("read_ini_result=" + read_ini_result); number of characters returned } else Debug.WriteLine("ERROR: ini file \"" + iniFile + "\" not found!"); } else returnCode.Append(name); return returnCode.ToString(); } /// /// Convert code from string to real address. If path is not blank, will pull from ini file. /// /// label in ini file or code /// path to ini file (OPTIONAL) /// size of address (default is 8) /// public UIntPtr GetCode(string name, string path = "", int size = 8) { string theCode = ""; if (mProc == null) return UIntPtr.Zero; if (mProc.Is64Bit) { //Debug.WriteLine("Changing to 64bit code..."); if (size == 8) size = 16; //change to 64bit return Get64BitCode(name, path, size); //jump over to 64bit code grab } if (!String.IsNullOrEmpty(path)) theCode = LoadCode(name, path); else theCode = name; if (String.IsNullOrEmpty(theCode)) { //Debug.WriteLine("ERROR: LoadCode returned blank. NAME:" + name + " PATH:" + path); return UIntPtr.Zero; } else { //Debug.WriteLine("Found code=" + theCode + " NAME:" + name + " PATH:" + path); } // remove spaces if (theCode.Contains(" ")) theCode = theCode.Replace(" ", String.Empty); if (!theCode.Contains("+") && !theCode.Contains(",")) { try { return new UIntPtr(Convert.ToUInt32(theCode, 16)); } catch { Console.WriteLine("Error in GetCode(). Failed to read address " + theCode); return UIntPtr.Zero; } } string newOffsets = theCode; if (theCode.Contains("+")) newOffsets = theCode.Substring(theCode.IndexOf('+') + 1); byte[] memoryAddress = new byte[size]; if (newOffsets.Contains(',')) { List offsetsList = new List(); string[] newerOffsets = newOffsets.Split(','); foreach (string oldOffsets in newerOffsets) { string test = oldOffsets; if (oldOffsets.Contains("0x")) test = oldOffsets.Replace("0x", ""); int preParse = 0; if (!oldOffsets.Contains("-")) preParse = Int32.Parse(test, NumberStyles.AllowHexSpecifier); else { test = test.Replace("-", ""); preParse = Int32.Parse(test, NumberStyles.AllowHexSpecifier); preParse = preParse * -1; } offsetsList.Add(preParse); } int[] offsets = offsetsList.ToArray(); if (theCode.Contains("base") || theCode.Contains("main")) ReadProcessMemory(mProc.Handle, (UIntPtr)((int)mProc.MainModule.BaseAddress + offsets[0]), memoryAddress, (UIntPtr)size, IntPtr.Zero); else if (!theCode.Contains("base") && !theCode.Contains("main") && theCode.Contains("+")) { string[] moduleName = theCode.Split('+'); IntPtr altModule = IntPtr.Zero; if (!moduleName[0].ToLower().Contains(".dll") && !moduleName[0].ToLower().Contains(".exe") && !moduleName[0].ToLower().Contains(".bin")) { string theAddr = moduleName[0]; if (theAddr.Contains("0x")) theAddr = theAddr.Replace("0x", ""); altModule = (IntPtr)Int32.Parse(theAddr, NumberStyles.HexNumber); } else { try { altModule = GetModuleAddressByName(moduleName[0]); } catch { Debug.WriteLine("Module " + moduleName[0] + " was not found in module list!"); //Debug.WriteLine("Modules: " + string.Join(",", mProc.Modules)); } } ReadProcessMemory(mProc.Handle, (UIntPtr)((int)altModule + offsets[0]), memoryAddress, (UIntPtr)size, IntPtr.Zero); } else ReadProcessMemory(mProc.Handle, (UIntPtr)(offsets[0]), memoryAddress, (UIntPtr)size, IntPtr.Zero); uint num1 = BitConverter.ToUInt32(memoryAddress, 0); //ToUInt64 causes arithmetic overflow. UIntPtr base1 = (UIntPtr)0; for (int i = 1; i < offsets.Length; i++) { base1 = new UIntPtr(Convert.ToUInt32(num1 + offsets[i])); ReadProcessMemory(mProc.Handle, base1, memoryAddress, (UIntPtr)size, IntPtr.Zero); num1 = BitConverter.ToUInt32(memoryAddress, 0); //ToUInt64 causes arithmetic overflow. } return base1; } else // no offsets { int trueCode = Convert.ToInt32(newOffsets, 16); IntPtr altModule = IntPtr.Zero; //Debug.WriteLine("newOffsets=" + newOffsets); if (theCode.ToLower().Contains("base") || theCode.ToLower().Contains("main")) altModule = mProc.MainModule.BaseAddress; else if (!theCode.ToLower().Contains("base") && !theCode.ToLower().Contains("main") && theCode.Contains("+")) { string[] moduleName = theCode.Split('+'); if (!moduleName[0].ToLower().Contains(".dll") && !moduleName[0].ToLower().Contains(".exe") && !moduleName[0].ToLower().Contains(".bin")) { string theAddr = moduleName[0]; if (theAddr.Contains("0x")) theAddr = theAddr.Replace("0x", ""); altModule = (IntPtr)Int32.Parse(theAddr, NumberStyles.HexNumber); } else { try { altModule = GetModuleAddressByName(moduleName[0]); } catch { Debug.WriteLine("Module " + moduleName[0] + " was not found in module list!"); //Debug.WriteLine("Modules: " + string.Join(",", mProc.Modules)); } } } else altModule = GetModuleAddressByName(theCode.Split('+')[0]); return (UIntPtr)((int)altModule + trueCode); } } /// /// Retrieve mProc.Process module baseaddress by name /// /// name of module /// public IntPtr GetModuleAddressByName(string name) { return mProc.Process.Modules.Cast().SingleOrDefault(m => string.Equals(m.ModuleName, name, StringComparison.OrdinalIgnoreCase)).BaseAddress; } /// /// Convert code from string to real address. If path is not blank, will pull from ini file. /// /// label in ini file OR code /// path to ini file (OPTIONAL) /// size of address (default is 16) /// public UIntPtr Get64BitCode(string name, string path = "", int size = 16) { string theCode = ""; if (!String.IsNullOrEmpty(path)) theCode = LoadCode(name, path); else theCode = name; if (String.IsNullOrEmpty(theCode)) return UIntPtr.Zero; // remove spaces if (theCode.Contains(" ")) theCode.Replace(" ", String.Empty); string newOffsets = theCode; if (theCode.Contains("+")) newOffsets = theCode.Substring(theCode.IndexOf('+') + 1); byte[] memoryAddress = new byte[size]; if (!theCode.Contains("+") && !theCode.Contains(",")) { try { return new UIntPtr(Convert.ToUInt64(theCode, 16)); } catch { Console.WriteLine("Error in GetCode(). Failed to read address " + theCode); return UIntPtr.Zero; } } if (newOffsets.Contains(',')) { List offsetsList = new List(); string[] newerOffsets = newOffsets.Split(','); foreach (string oldOffsets in newerOffsets) { string test = oldOffsets; if (oldOffsets.Contains("0x")) test = oldOffsets.Replace("0x", ""); Int64 preParse = 0; if (!oldOffsets.Contains("-")) preParse = Int64.Parse(test, NumberStyles.AllowHexSpecifier); else { test = test.Replace("-", ""); preParse = Int64.Parse(test, NumberStyles.AllowHexSpecifier); preParse = preParse * -1; } offsetsList.Add(preParse); } Int64[] offsets = offsetsList.ToArray(); if (theCode.Contains("base") || theCode.Contains("main")) ReadProcessMemory(mProc.Handle, (UIntPtr)((Int64)mProc.MainModule.BaseAddress + offsets[0]), memoryAddress, (UIntPtr)size, IntPtr.Zero); else if (!theCode.Contains("base") && !theCode.Contains("main") && theCode.Contains("+")) { string[] moduleName = theCode.Split('+'); IntPtr altModule = IntPtr.Zero; if (!moduleName[0].ToLower().Contains(".dll") && !moduleName[0].ToLower().Contains(".exe") && !moduleName[0].ToLower().Contains(".bin")) altModule = (IntPtr)Int64.Parse(moduleName[0], System.Globalization.NumberStyles.HexNumber); else { try { altModule = GetModuleAddressByName(moduleName[0]); } catch { Debug.WriteLine("Module " + moduleName[0] + " was not found in module list!"); //Debug.WriteLine("Modules: " + string.Join(",", mProc.Modules)); } } ReadProcessMemory(mProc.Handle, (UIntPtr)((Int64)altModule + offsets[0]), memoryAddress, (UIntPtr)size, IntPtr.Zero); } else // no offsets ReadProcessMemory(mProc.Handle, (UIntPtr)(offsets[0]), memoryAddress, (UIntPtr)size, IntPtr.Zero); Int64 num1 = BitConverter.ToInt64(memoryAddress, 0); UIntPtr base1 = (UIntPtr)0; for (int i = 1; i < offsets.Length; i++) { base1 = new UIntPtr(Convert.ToUInt64(num1 + offsets[i])); ReadProcessMemory(mProc.Handle, base1, memoryAddress, (UIntPtr)size, IntPtr.Zero); num1 = BitConverter.ToInt64(memoryAddress, 0); } return base1; } else { Int64 trueCode = Convert.ToInt64(newOffsets, 16); IntPtr altModule = IntPtr.Zero; if (theCode.Contains("base") || theCode.Contains("main")) altModule = mProc.MainModule.BaseAddress; else if (!theCode.Contains("base") && !theCode.Contains("main") && theCode.Contains("+")) { string[] moduleName = theCode.Split('+'); if (!moduleName[0].ToLower().Contains(".dll") && !moduleName[0].ToLower().Contains(".exe") && !moduleName[0].ToLower().Contains(".bin")) { string theAddr = moduleName[0]; if (theAddr.Contains("0x")) theAddr = theAddr.Replace("0x", ""); altModule = (IntPtr)Int64.Parse(theAddr, NumberStyles.HexNumber); } else { try { altModule = GetModuleAddressByName(moduleName[0]); } catch { Debug.WriteLine("Module " + moduleName[0] + " was not found in module list!"); //Debug.WriteLine("Modules: " + string.Join(",", mProc.Modules)); } } } else altModule = GetModuleAddressByName(theCode.Split('+')[0]); return (UIntPtr)((Int64)altModule + trueCode); } } public string MSize() { if (mProc.Is64Bit) return ("x16"); else return ("x8"); } } } ================================================ FILE: XAU/Util/XboxAuthNet/Base64UrlHelper.cs ================================================ // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/9895855ac4fcf52893fbc2b06ee20ea3eda1549a/src/client/Microsoft.Identity.Client/Utils/Base64UrlHelpers.cs // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; using System.Diagnostics; using System.Globalization; using System.Text; namespace XboxAuthNet { // Based on https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/1698/files internal static class Base64UrlHelper { private const char base64PadCharacter = '='; #if NET45 private const string doubleBase64PadCharacter = "=="; #endif private const char base64Character62 = '+'; private const char base64Character63 = '/'; private const char base64UrlCharacter62 = '-'; private const char base64UrlCharacter63 = '_'; /// /// Encoding table /// internal static readonly char[] s_base64Table = { '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', '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', '0','1','2','3','4','5','6','7','8','9', base64UrlCharacter62, base64UrlCharacter63 }; /// /// The following functions perform base64url encoding which differs from regular base64 encoding as follows /// * padding is skipped so the pad character '=' doesn't have to be percent encoded /// * the 62nd and 63rd regular base64 encoding characters ('+' and '/') are replace with ('-' and '_') /// The changes make the encoding alphabet file and URL safe. /// /// string to encode. /// Base64Url encoding of the UTF8 bytes. public static string Encode(string arg) { return Encode(Encoding.UTF8.GetBytes(arg)); } /// /// 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 /// the subset as an offset in the input array, and the number of elements in the array to convert. /// /// An array of 8-bit unsigned integers. /// An offset in inArray. /// The number of elements of inArray to convert. /// The string representation in base 64 url encoding of length elements of inArray, starting at position offset. /// 'inArray' is null. /// offset or length is negative OR offset plus length is greater than the length of inArray. private static string Encode(byte[] inArray, int offset, int length) { _ = inArray ?? throw new ArgumentNullException(nameof(inArray)); if (length == 0) return string.Empty; if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); if (offset < 0 || inArray.Length < offset) throw new ArgumentOutOfRangeException(nameof(offset)); if (inArray.Length < offset + length) throw new ArgumentOutOfRangeException(nameof(length)); int lengthmod3 = length % 3; int limit = offset + (length - lengthmod3); char[] output = new char[(length + 2) / 3 * 4]; char[] table = s_base64Table; int i, j = 0; // takes 3 bytes from inArray and insert 4 bytes into output for (i = offset; i < limit; i += 3) { byte d0 = inArray[i]; byte d1 = inArray[i + 1]; byte d2 = inArray[i + 2]; output[j + 0] = table[d0 >> 2]; output[j + 1] = table[((d0 & 0x03) << 4) | (d1 >> 4)]; output[j + 2] = table[((d1 & 0x0f) << 2) | (d2 >> 6)]; output[j + 3] = table[d2 & 0x3f]; j += 4; } //Where we left off before i = limit; switch (lengthmod3) { case 2: { byte d0 = inArray[i]; byte d1 = inArray[i + 1]; output[j + 0] = table[d0 >> 2]; output[j + 1] = table[((d0 & 0x03) << 4) | (d1 >> 4)]; output[j + 2] = table[(d1 & 0x0f) << 2]; j += 3; } break; case 1: { byte d0 = inArray[i]; output[j + 0] = table[d0 >> 2]; output[j + 1] = table[(d0 & 0x03) << 4]; j += 2; } break; //default or case 0: no further operations are needed. } return new string(output, 0, j); } /// /// 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 /// the subset as an offset in the input array, and the number of elements in the array to convert. /// /// An array of 8-bit unsigned integers. /// The string representation in base 64 url encoding of length elements of inArray, starting at position offset. /// 'inArray' is null. /// offset or length is negative OR offset plus length is greater than the length of inArray. public static string Encode(byte[] inArray) { return Encode(inArray, 0, inArray.Length); } internal static string EncodeString(string str) { return Encode(Encoding.UTF8.GetBytes(str)); } /// /// Converts the specified string, which encodes binary data as base-64-url digits, to an equivalent 8-bit unsigned integer array. /// base64Url encoded string. /// UTF8 bytes. public static byte[] DecodeBytes(string str) { #if NET45 // 62nd char of encoding str = str.Replace(base64UrlCharacter62, base64Character62); // 63rd char of encoding str = str.Replace(base64UrlCharacter63, base64Character63); // check for padding switch (str.Length % 4) { case 0: // No pad chars in this case break; case 2: // Two pad chars str += doubleBase64PadCharacter; break; case 3: // One pad char str += base64PadCharacter; break; default: throw new FormatException($"Unable to decode: {str} as Base64url encoded string."); } return Convert.FromBase64String(str); #else return UnsafeDecode(str); #endif } #if !NET45 private unsafe static byte[] UnsafeDecode(string str) { int mod = str.Length % 4; if (mod == 1) throw new FormatException($"Unable to decode: {str} as Base64url encoded string."); bool needReplace = false; int decodedLength = str.Length + (4 - mod) % 4; for (int i = 0; i < str.Length; i++) { if (str[i] == base64UrlCharacter62 || str[i] == base64UrlCharacter63) { needReplace = true; break; } } if (needReplace) { string decodedString = new string(char.MinValue, decodedLength); fixed (char* dest = decodedString) { int i = 0; for (; i < str.Length; i++) { if (str[i] == base64UrlCharacter62) dest[i] = base64Character62; else if (str[i] == base64UrlCharacter63) dest[i] = base64Character63; else dest[i] = str[i]; } for (; i < decodedLength; i++) dest[i] = base64PadCharacter; } return Convert.FromBase64String(decodedString); } else { if (decodedLength == str.Length) { return Convert.FromBase64String(str); } else { string decodedString = new string(char.MinValue, decodedLength); fixed (char* src = str) fixed (char* dest = decodedString) { Buffer.MemoryCopy(src, dest, str.Length * 2, str.Length * 2); dest[str.Length] = base64PadCharacter; if (str.Length + 2 == decodedLength) dest[str.Length + 1] = base64PadCharacter; } return Convert.FromBase64String(decodedString); } } } #endif /// /// Decodes the string from Base64UrlEncoded to UTF8. /// /// string to decode. /// UTF8 string. public static string Decode(string arg) { return Encoding.UTF8.GetString(DecodeBytes(arg)); } } } ================================================ FILE: XAU/Util/XboxAuthNet/ErrorCodes.cs ================================================ namespace XboxAuthNet { // https://github.com/microsoft/xbox-live-api/blob/f1a347b91f5f5dae62c35623719ecf8b9ba68746/Source/Shared/errors_legacy.h#L875 public class ErrorCodes { /// /// 0x8015DC00 /// Developer mode is not authorized for the client device. /// const uint XO_E_DEVMODE_NOT_AUTHORIZED = (uint)0x8015DC00, /// /// 0x8015DC01 /// A system update is required before this action can be performed. /// XO_E_SYSTEM_UPDATE_REQUIRED = (uint)0x8015DC01, /// /// 0x8015DC02 /// A content update is required before this action can be performed. /// XO_E_CONTENT_UPDATE_REQUIRED = (uint)0x8015DC02, /// /// 0x8015DC03 /// The device or user was banned. /// XO_E_ENFORCEMENT_BAN = (uint)0x8015DC03, /// /// 0x8015DC04 /// The device or user was banned. /// XO_E_THIRD_PARTY_BAN = (uint)0x8015DC04, /// /// 0x8015DC05 /// Access to this resource has been parentally restricted. /// XO_E_ACCOUNT_PARENTALLY_RESTRICTED = (uint)0x8015DC05, /// /// 0x8015DC08 /// Access to this resource requires that the account billing information /// is updated. /// XO_E_ACCOUNT_BILLING_MAINTENANCE_REQUIRED = (uint)0x8015DC08, /// /// 0x8015DC0A /// The user has not accepted the terms of use for this resource. /// XO_E_ACCOUNT_TERMS_OF_USE_NOT_ACCEPTED = (uint)0x8015DC0A, /// /// 0x8015DC0B /// This resource is not available in the country associated with the user. /// XO_E_ACCOUNT_COUNTRY_NOT_AUTHORIZED = (uint)0x8015DC0B, /// /// 0x8015DC0C /// Access to this resource requires age verification. /// XO_E_ACCOUNT_AGE_VERIFICATION_REQUIRED = (uint)0x8015DC0C, /// /// 0x8015DC0D /// XO_E_ACCOUNT_CURFEW = (uint)0x8015DC0D, /// /// 0x8015DC0E /// XO_E_ACCOUNT_CHILD_NOT_IN_FAMILY = (uint)0x8015DC0E, /// /// 0x8015DC0F /// XO_E_ACCOUNT_CSV_TRANSITION_REQUIRED = (uint)0x8015DC0F, /// /// 0x8015DC09 /// XO_E_ACCOUNT_CREATION_REQUIRED = (uint)0x8015DC09, /// /// 0x8015DC10 /// XO_E_ACCOUNT_MAINTENANCE_REQUIRED = (uint)0x8015DC10, /// /// 0x8015DC11 /// The call was blocked because there was a conflict with the sandbox, console, application, or /// your account.Verify your account, console and title settings in XDP, and check the current /// Sandbox on the device. /// XO_E_ACCOUNT_TYPE_NOT_ALLOWED = (uint)0x8015DC11, /// /// 0x8015DC12 /// Your device does not have access to the Sandbox it is set to, or the account you are signed /// in with does not have access to the Sandbox.Check that you are using the correct Sandbox. /// /// Note: All XDK samples use XDKS.1 SandboxID, which allow all user accounts to access and run /// the samples.SandboxID's are case sensitive- Not matching the case of your SandboxID exactly may /// result in errors. If you are still having issues running the sample, please work with your /// Developer Account Manager and provide a fiddler trace to help with troubleshooting. /// /// For more information on handling this error, please see the "Troubleshooting Sign-in" article /// in the Xbox Live documentation /// XO_E_CONTENT_ISOLATION = (uint)0x8015DC12, /// /// 0x8015DC13 /// XO_E_ACCOUNT_NAME_CHANGE_REQUIRED = (uint)0x8015DC13, /// /// 0x8015DC14 /// XO_E_DEVICE_CHALLENGE_REQUIRED = (uint)0x8015DC14, /// /// 0x8015DC16 /// The account was signed in on another device. /// XO_E_SIGNIN_COUNT_BY_DEVICE_TYPE_EXCEEDED = (uint)0x8015DC16, /// /// 0x8015DC17 /// XO_E_PIN_CHALLENGE_REQUIRED = (uint)0x8015DC17, /// /// 0x8015DC18 /// XO_E_RETAIL_ACCOUNT_NOT_ALLOWED = (uint)0x8015DC18, /// /// 0x8015DC19 /// The current sandbox is not allowed to access the SCID. Please ensure that your current /// sandbox is set to your development sandbox. If you are running on a Windows 10 PC, then /// you can change your current sandbox using the SwitchSandbox.cmd script in the Xbox Live SDK /// tools directory. If you are using an Xbox One, you can switch the sandbox using Xbox One /// Manager. /// /// For more information on handling this error, please see the "Troubleshooting Sign-in" article /// in the Xbox Live documentation. /// XO_E_SANDBOX_NOT_ALLOWED = (uint)0x8015DC19, /// /// 0x8015DC1A /// XO_E_ACCOUNT_SERVICE_UNAVAILABLE_UNKNOWN_USER = (uint)0x8015DC1A, /// /// 0x8015DC1B /// XO_E_GREEN_SIGNED_CONTENT_NOT_AUTHORIZED = (uint)0x8015DC1B, /// /// 0x8015DC1C /// XO_E_CONTENT_NOT_AUTHORIZED = (uint)0x8015DC1C; } } ================================================ FILE: XAU/Util/XboxAuthNet/ErrorHelper.cs ================================================ using System; namespace XboxAuthNet { internal static class ErrorHelper { public static string? ConvertToHexErrorCode(string? errorCode) { if (!string.IsNullOrEmpty(errorCode)) { var errorInt = long.Parse(errorCode); errorCode = errorInt.ToString("x"); } return errorCode; } public static string? TryConvertToHexErrorCode(string? errorCode) { try { return ConvertToHexErrorCode(errorCode); } catch (FormatException) { return errorCode; } catch (OverflowException) { return errorCode; } } } } ================================================ FILE: XAU/Util/XboxAuthNet/FodyWeavers.xml ================================================  ================================================ FILE: XAU/Util/XboxAuthNet/HttpHelper.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Net.Http.Json; using System.Web; using System.Text.Json; namespace XboxAuthNet { public class HttpHelper { public const string UserAgent = "Mozilla/5.0 (XboxReplay; XboxLiveAuth/3.0) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/71.0.3578.98 Safari/537.36"; public static string GetQueryString(Dictionary queries) { return string.Join("&", queries.Select(x => $"{x.Key}={HttpUtility.UrlEncode(x.Value)}")); } public static JsonContent CreateJsonContent(T obj) { return JsonContent.Create(obj, mediaType: new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"), options: new JsonSerializerOptions { DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, PropertyNamingPolicy = null }); } } } ================================================ FILE: XAU/Util/XboxAuthNet/Jwt/JwtDecoder.cs ================================================ using System; using System.Text; using System.Text.Json; namespace XboxAuthNet.Jwt { internal static class JwtDecoder { /// /// decode jwt payload /// /// entire jwt /// decoded jwt payload /// /// invalid jwt internal static string DecodePayloadString(string jwt) { if (string.IsNullOrEmpty(jwt)) throw new ArgumentNullException(jwt); string[] spl = jwt.Split('.'); if (spl.Length != 3) throw new FormatException("invalid jwt"); string encodedPayload = spl[1]; switch (encodedPayload.Length % 4) { case 0: break; case 2: encodedPayload += "=="; break; case 3: encodedPayload += "="; break; default: throw new FormatException("jwt payload"); } string decodedPayload = Encoding.UTF8.GetString(Convert.FromBase64String(encodedPayload)); // encodedPayload can't be null since string.Split never return null element return decodedPayload; } /// /// decode jwt payload and deserialize /// /// /// deserialized object of jwt payload /// /// invalid jwt internal static T? DecodePayload(string jwt) where T : class { string payload = DecodePayloadString(jwt); return JsonSerializer.Deserialize(payload); } } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/AuthCodeException.cs ================================================ namespace XboxAuthNet.OAuth.CodeFlow; internal class AuthCodeException : Exception { public AuthCodeException(string? error, string? errorDescription) : base(error ?? errorDescription) { Error = error; ErrorDescription = errorDescription; } public string? Error { get; } public string? ErrorDescription { get; } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/CodeFlowAuthenticator.cs ================================================ using XboxAuthNet.OAuth.CodeFlow.Parameters; namespace XboxAuthNet.OAuth.CodeFlow; public class CodeFlowAuthenticator { private readonly ICodeFlowApiClient _client; private readonly ICodeFlowUrlChecker _uriChecker; private readonly IWebUI _ui; internal CodeFlowAuthenticator( ICodeFlowApiClient client, IWebUI ui, ICodeFlowUrlChecker urlChecker) { _client = client; _ui = ui; _uriChecker = urlChecker; } public Task AuthenticateInteractively(CancellationToken cancellationToken = default) => AuthenticateInteractively( new CodeFlowAuthorizationParameter(), cancellationToken); public async Task AuthenticateInteractively( CodeFlowAuthorizationParameter parameter, CancellationToken cancellationToken = default) { var uri = _client.CreateAuthorizeCodeUrl(parameter); var authCode = await _ui.DisplayDialogAndInterceptUri( new Uri(uri), _uriChecker, cancellationToken); if (!authCode.IsSuccess) { throw new AuthCodeException(authCode.Error, authCode.ErrorDescription); } var tokenParameter = new CodeFlowAccessTokenParameter { Code = authCode.Code }; tokenParameter.RedirectUrl = parameter.RedirectUri; return await _client.GetAccessToken(tokenParameter, cancellationToken); } public Task AuthenticateSilently( string refreshToken, CancellationToken cancellationToken = default) { var parameter = new CodeFlowRefreshTokenParameter { RefreshToken = refreshToken }; return _client.RefreshToken(parameter, cancellationToken); } public async Task Signout(CancellationToken cancellationToken = default) { var url = _client.CreateSignoutUrl(); await _ui.DisplayDialogAndNavigateUri(new Uri(url), cancellationToken); } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/CodeFlowAuthorizationResult.cs ================================================ namespace XboxAuthNet.OAuth.CodeFlow; public struct CodeFlowAuthorizationResult { public string? Code { get; set; } public string? IdToken { get; set; } public string? State { get; set; } public string? Error { get; set; } public string? ErrorDescription { get; set; } public bool IsSuccess => !string.IsNullOrEmpty(Code) && string.IsNullOrEmpty(Error); public bool IsEmpty => string.IsNullOrEmpty(Code) && string.IsNullOrEmpty(Error) && string.IsNullOrEmpty(ErrorDescription); } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/CodeFlowBuilder.cs ================================================ namespace XboxAuthNet.OAuth.CodeFlow; public class CodeFlowBuilder { private readonly ICodeFlowApiClient _apiClient; public CodeFlowBuilder(ICodeFlowApiClient apiClient) { this._apiClient = apiClient; } private WebUIOptions? uiOptions; private IWebUI? webUI; private ICodeFlowUrlChecker? uriChecker; public CodeFlowBuilder WithUIParent(object parent) { uiOptions ??= createDefaultWebUIOptions(); uiOptions.ParentObject = parent; return this; } public CodeFlowBuilder WithUITitle(string title) { uiOptions ??= createDefaultWebUIOptions(); uiOptions.Title = title; return this; } public CodeFlowBuilder WithUIOptions(WebUIOptions options) { this.uiOptions = options; return this; } public CodeFlowBuilder WithWebUI(IWebUI ui) { this.webUI = ui; return this; } public CodeFlowBuilder WithWebUI(Func factory) { this.uiOptions ??= createDefaultWebUIOptions(); WithWebUI(factory.Invoke(this.uiOptions)); return this; } public CodeFlowBuilder WithUriChecker(ICodeFlowUrlChecker checker) { this.uriChecker = checker; return this; } public CodeFlowAuthenticator Build() { uriChecker ??= createDefaultUriChecker(); webUI ??= createDefaultWebUIForPlatform(); return new CodeFlowAuthenticator(_apiClient, webUI, uriChecker); } private ICodeFlowUrlChecker createDefaultUriChecker() { return new CodeFlowUrlChecker(); } private IWebUI createDefaultWebUIForPlatform() { this.uiOptions ??= createDefaultWebUIOptions(); return PlatformManager.CurrentPlatform.CreateWebUI(uiOptions); } private WebUIOptions createDefaultWebUIOptions() => new WebUIOptions { ParentObject = null, SynchronizationContext = SynchronizationContext.Current }; } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/CodeFlowLiveApiClient.cs ================================================ using System.Net.Http; using XboxAuthNet.OAuth.CodeFlow.Parameters; namespace XboxAuthNet.OAuth.CodeFlow; public class CodeFlowLiveApiClient : ICodeFlowApiClient { public const string OAuthDesktop = "https://login.live.com/oauth20_desktop.srf"; public const string OAuthAuthorize = "https://login.live.com/oauth20_authorize.srf"; public const string OAuthErrorPath = "/err.srf"; public const string OAuthToken = "https://login.live.com/oauth20_token.srf"; private readonly HttpClient httpClient; public CodeFlowLiveApiClient(string clientId, string scope, HttpClient client) { ClientId = clientId; Scope = scope; httpClient = client; } public string ClientId { get; } public string Scope { get; } public string CreateAuthorizeCodeUrl(CodeFlowAuthorizationParameter parameter) { setCommonParameters(parameter); if (string.IsNullOrEmpty(parameter.RedirectUri)) parameter.RedirectUri = OAuthDesktop; if (string.IsNullOrEmpty(parameter.ResponseType)) parameter.ResponseType = "code"; if (string.IsNullOrEmpty(parameter.ResponseMode)) parameter.ResponseMode = "query"; if (string.IsNullOrEmpty(parameter.Prompt)) parameter.Prompt = "select_account"; var query = parameter.ToQueryDictionary(); return OAuthAuthorize + "?" + HttpHelper.GetQueryString(query); } public string CreateSignoutUrl() => CreateSignoutUrl("consumer"); public string CreateSignoutUrl(string tenant) => $"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/logout"; public Task GetAccessToken( CodeFlowAccessTokenParameter parameter, CancellationToken cancellationToken) => requestToken(setAccessTokenParameters(parameter), cancellationToken); private CodeFlowAccessTokenParameter setAccessTokenParameters(CodeFlowAccessTokenParameter parameters) { if (string.IsNullOrEmpty(parameters.RedirectUrl)) parameters.RedirectUrl = OAuthDesktop; if (string.IsNullOrEmpty(parameters.GrantType)) parameters.GrantType = "authorization_code"; return parameters; } public Task RefreshToken( CodeFlowRefreshTokenParameter parameter, CancellationToken cancellationToken) => requestToken(setRefreshTokenParameters(parameter), cancellationToken); private CodeFlowRefreshTokenParameter setRefreshTokenParameters(CodeFlowRefreshTokenParameter parameters) { if (string.IsNullOrEmpty(parameters.GrantType)) parameters.GrantType = "refresh_token"; return parameters; } private async Task requestToken( CodeFlowParameter parameter, CancellationToken cancellationToken) { setCommonParameters(parameter); var queryDict = parameter.ToQueryDictionary(); return await microsoftOAuthRequest(new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new Uri(OAuthToken), Content = new FormUrlEncodedContent(queryDict!) }, cancellationToken).ConfigureAwait(false); } private async Task microsoftOAuthRequest( HttpRequestMessage req, CancellationToken cancellationToken) { req.Headers.Add("User-Agent", HttpHelper.UserAgent); req.Headers.Add("Accept-Encoding", "gzip"); req.Headers.Add("Accept-Language", "en-US"); var res = await httpClient.SendAsync(req, cancellationToken) .ConfigureAwait(false); var resBody = await res.Content.ReadAsStringAsync() .ConfigureAwait(false); var statusCode = (int)res.StatusCode; var reasonPhrase = res.ReasonPhrase; return MicrosoftOAuthResponse.FromHttpResponse(resBody, statusCode, reasonPhrase); } private void setCommonParameters(CodeFlowParameter parameter) { parameter.Tenant = "consumer"; if (string.IsNullOrEmpty(parameter.ClientId)) parameter.ClientId = ClientId; if (string.IsNullOrEmpty(parameter.Scope)) parameter.Scope = Scope; } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/CodeFlowUriChecker.cs ================================================ using System.Web; namespace XboxAuthNet.OAuth.CodeFlow; public class CodeFlowUrlChecker : ICodeFlowUrlChecker { public CodeFlowAuthorizationResult GetAuthCodeResult(Uri uri) { var query = HttpUtility.ParseQueryString(uri.Query); var authCode = new CodeFlowAuthorizationResult { Code = query["code"], IdToken = query["id_token"], State = query["state"], Error = query["error"], ErrorDescription = HttpUtility.UrlDecode(query["error_description"]) }; return authCode; } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/ICodeFlowApiClient.cs ================================================ using XboxAuthNet.OAuth.CodeFlow.Parameters; namespace XboxAuthNet.OAuth.CodeFlow; public interface ICodeFlowApiClient { string CreateAuthorizeCodeUrl(CodeFlowAuthorizationParameter parameter); string CreateSignoutUrl(); Task GetAccessToken( CodeFlowAccessTokenParameter parameter, CancellationToken cancellationToken); Task RefreshToken( CodeFlowRefreshTokenParameter parameter, CancellationToken cancellationToken); } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/ICodeFlowUrlChecker.cs ================================================ namespace XboxAuthNet.OAuth.CodeFlow { public interface ICodeFlowUrlChecker { CodeFlowAuthorizationResult GetAuthCodeResult(Uri uri); } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/IWebUI.cs ================================================ // This code is from MSAL.NET namespace XboxAuthNet.OAuth.CodeFlow; public interface IWebUI { Task DisplayDialogAndInterceptUri(Uri uri, ICodeFlowUrlChecker uriChecker, CancellationToken cancellationToken); Task DisplayDialogAndNavigateUri(Uri uri, CancellationToken cancellationToken); } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/MicrosoftOAuthPromptModes.cs ================================================ namespace XboxAuthNet.OAuth.CodeFlow; public static class MicrosoftOAuthPromptModes { /// /// Forces the user to enter their credentials on that request, negating single-sign on. /// public const string Login = "login"; /// /// Ensures that the user isn't presented with any interactive prompt. /// If the request can't be completed silently by using sigle-sign on, /// the Microsoft identity platform returns an `interaction_required` error. /// public const string None = "none"; /// /// Triggers the OAuth consent dialog after the user signs in, asking the user to grant permissions to the app. /// public const string Consent = "consent"; /// /// Interrupts single sign-on providing account selection experience listing all the accounts /// either in session or any remembered account or an option to choose to use a different account altogether. /// public const string SelectAccount = "select_account"; } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/Parameters/CodeFlowAccessTokenParameter.cs ================================================ using System.Text.Json.Serialization; namespace XboxAuthNet.OAuth.CodeFlow.Parameters; public class CodeFlowAccessTokenParameter : CodeFlowParameter { public const string DefaultClientAssertionType = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; [JsonPropertyName("code")] public string? Code { get; set; } [JsonPropertyName("redirect_uri")] public string? RedirectUrl { get; set; } [JsonPropertyName("grant_type")] public string? GrantType { get; set; } [JsonPropertyName("code_verifier")] public string? CodeVerifier { get; set; } [JsonPropertyName("client_secret")] public string? ClientSecret { get; set; } [JsonPropertyName("client_assertion_type")] public string? ClientAssertionType { get; set; } [JsonPropertyName("client_assertion")] public string? CilentAssertion { get; set; } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/Parameters/CodeFlowAuthorizationParameter.cs ================================================ using System.Text.Json.Serialization; namespace XboxAuthNet.OAuth.CodeFlow.Parameters; public class CodeFlowAuthorizationParameter : CodeFlowParameter { /// /// response_type: id_token, token, code /// [JsonPropertyName("response_type")] public string? ResponseType { get; set; } /// /// redirect_uri /// [JsonPropertyName("redirect_uri")] public string? RedirectUri { get; set; } /// /// response_mode: query, fragment, form_post /// [JsonPropertyName("response_mode")] public string? ResponseMode { get; set; } /// /// state /// [JsonPropertyName("state")] public string? State { get; set; } /// /// prompt: login, none, consent, select_account /// [JsonPropertyName("prompt")] public string? Prompt { get; set; } /// /// login_hint /// [JsonPropertyName("login_hint")] public string? LoginHint { get; set; } /// /// domain_hint /// [JsonPropertyName("domain_hint")] public string? DomainHint { get; set; } /// /// code_challenge /// [JsonPropertyName("code_challenge")] public string? CodeChallenge { get; set; } /// /// code_challenge_method /// [JsonPropertyName("code_challenge_method")] public string? CodeChallengeMethod { get; set; } [JsonPropertyName("nonce")] public string? Nonce { get; set; } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/Parameters/CodeFlowParameter.cs ================================================ using System.Text.Json.Serialization; namespace XboxAuthNet.OAuth.CodeFlow.Parameters; public class CodeFlowParameter { public string? Tenant { get; set; } [JsonPropertyName("client_id")] public string? ClientId { get; set; } [JsonPropertyName("scope")] public string? Scope { get; set; } public Dictionary ExtraQueries { get; } = new(); public Dictionary ToQueryDictionary() { var query = new Dictionary(); var props = GetType().GetProperties(); foreach (var prop in props) { var value = prop.GetMethod?.Invoke(this, null)?.ToString(); if (string.IsNullOrEmpty(value)) continue; var attr = prop .GetCustomAttributes(typeof(JsonPropertyNameAttribute), true) .FirstOrDefault(); if (attr is not JsonPropertyNameAttribute jsonAttr) continue; var propName = jsonAttr.Name; query[propName] = value; } foreach (var kv in ExtraQueries) { query[kv.Key] = kv.Value; } return query; } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/Parameters/CodeFlowRefreshTokenParameter.cs ================================================ using System.Text.Json.Serialization; namespace XboxAuthNet.OAuth.CodeFlow.Parameters; public class CodeFlowRefreshTokenParameter : CodeFlowParameter { [JsonPropertyName("grant_type")] public string? GrantType { get; set; } [JsonPropertyName("client_secret")] public string? ClientSecret { get; set; } [JsonPropertyName("refresh_token")] public string? RefreshToken { get; set; } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/CodeFlow/WebUIOptions.cs ================================================ namespace XboxAuthNet.OAuth.CodeFlow; public class WebUIOptions { public object? ParentObject { get; set; } public string? Title { get; set; } public SynchronizationContext? SynchronizationContext { get; set; } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/MicrosoftOAuthException.cs ================================================ using System.Text.Json; namespace XboxAuthNet.OAuth; public class MicrosoftOAuthException : Exception { public MicrosoftOAuthException() { } public MicrosoftOAuthException(string? message, int statusCode) : base(message) => StatusCode = statusCode; public MicrosoftOAuthException(string? error, string? errorDes, int[]? codes, int statusCode) : base(CreateMessageFromError(error, errorDes)) => (StatusCode, Error, ErrorDescription, ErrorCodes) = (statusCode, error, errorDes, codes); public int StatusCode { get; private set; } public string? Error { get; private set; } public string? ErrorDescription { get; private set; } public int[]? ErrorCodes { get; private set; } private static string CreateMessageFromError(string? error, string? errorDes) { return string.Join(", ", new string?[] { error, errorDes }.Where(x => !string.IsNullOrEmpty(x))); } public static MicrosoftOAuthException FromResponseBody(string resBody, int statusCode, string? reasonPhrase) { try { throw MicrosoftOAuthException.fromJsonBody(resBody, statusCode); } catch (FormatException) { if (string.IsNullOrEmpty(reasonPhrase)) throw new MicrosoftOAuthException(statusCode.ToString(), statusCode); else throw new MicrosoftOAuthException($"{statusCode}: {reasonPhrase}", statusCode); } } private static MicrosoftOAuthException fromJsonBody(string responseBody, int statusCode) { try { using var doc = JsonDocument.Parse(responseBody); var root = doc.RootElement; string? error = null; string? errorDes = null; int[]? errorCodes = null; if (root.TryGetProperty("error", out var errorProp) && errorProp.ValueKind == JsonValueKind.String) error = errorProp.GetString(); if (root.TryGetProperty("error_description", out var errorDesProp) && errorDesProp.ValueKind == JsonValueKind.String) errorDes = errorDesProp.GetString(); if (root.TryGetProperty("error_codes", out var errorCodesProp) && errorCodesProp.ValueKind == JsonValueKind.Array) { errorCodes = errorCodesProp .EnumerateArray() .Where(x => x.ValueKind == JsonValueKind.Number) .Select(x => x.GetInt32()) .ToArray(); } if (string.IsNullOrEmpty(error) && string.IsNullOrEmpty(errorDes)) throw new FormatException(); return new MicrosoftOAuthException(error, errorDes, errorCodes, statusCode); } catch (JsonException) { throw new FormatException(); } } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/MicrosoftOAuthResponse.cs ================================================ using System.Text.Json; using System.Text.Json.Serialization; using XboxAuthNet.Jwt; namespace XboxAuthNet.OAuth; public class MicrosoftOAuthResponse { [JsonPropertyName("access_token")] public string? AccessToken { get; set; } [JsonPropertyName("token_type")] public string? TokenType { get; set; } [JsonPropertyName("expires_in")] public int ExpireIn { get; set; } [JsonPropertyName("expires_on")] public DateTimeOffset ExpiresOn { get; set; } [JsonPropertyName("scope")] public string? Scope { get; set; } [JsonPropertyName("refresh_token")] public string? RawRefreshToken { get; set; } [JsonIgnore] public string? RefreshToken { get => RawRefreshToken?.Split('.')?.Last(); set => RawRefreshToken = "M.R3_BAY." + value; } [JsonPropertyName("id_token")] public string? IdToken { get; set; } public MicrosoftUserPayload? DecodeIdTokenPayload() { if (string.IsNullOrEmpty(IdToken)) return null; return JwtDecoder.DecodePayload(IdToken!); } public bool Validate() { if (string.IsNullOrEmpty(AccessToken)) return false; if (DateTime.UtcNow > ExpiresOn) return false; return true; } public static MicrosoftOAuthResponse FromHttpResponse(string resBody, int statusCode, string? reasonPhrase) { if (statusCode / 100 != 2) throw MicrosoftOAuthException.FromResponseBody(resBody, statusCode, reasonPhrase); try { var resObj = JsonSerializer.Deserialize(resBody); if (resObj == null) throw new MicrosoftOAuthException("The response was empty.", statusCode); if (resObj.ExpiresOn == default) resObj.ExpiresOn = DateTimeOffset.UtcNow.AddSeconds(resObj.ExpireIn); return resObj; } catch (JsonException) { throw MicrosoftOAuthException.FromResponseBody(resBody, statusCode, reasonPhrase); } } } ================================================ FILE: XAU/Util/XboxAuthNet/OAuth/MicrosoftUserPayload.cs ================================================ using System.Text.Json.Serialization; namespace XboxAuthNet.OAuth; // https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens#payload-claims public class MicrosoftUserPayload { [JsonPropertyName("aud")] public string? ApplicationId { get; set; } [JsonPropertyName("iss")] public string? Issuer { get; set; } [JsonPropertyName("iat")] public long IssuedAt { get; set; } [JsonPropertyName("idp")] public string? IdentityProvider { get; set; } [JsonPropertyName("nbf")] public long NotBefore { get; set; } [JsonPropertyName("exp")] public long ExpiresOn { get; set; } [JsonPropertyName("c_hash")] public string? CodeHash { get; set; } [JsonPropertyName("at_hash")] public string? AccessTokenHash { get; set; } [JsonPropertyName("preferred_username")] public string? Username { get; set; } [JsonPropertyName("email")] public string? Email { get; set; } [JsonPropertyName("name")] public string? Name { get; set; } [JsonPropertyName("nonce")] public string? Nonce { get; set; } [JsonPropertyName("oid")] public string? UserId { get; set; } [JsonPropertyName("roles")] public string[]? Roles { get; set; } [JsonPropertyName("sub")] public string? Subject { get; set; } [JsonPropertyName("tid")] public string? Tenant { get; set; } [JsonPropertyName("unique_name")] public string? UniqueName { get; set; } [JsonPropertyName("uti")] public string? TokenIdentifier { get; set; } [JsonPropertyName("ver")] public string? Version { get; set; } [JsonPropertyName("hasgroups")] public bool HasGroups { get; set; } } ================================================ FILE: XAU/Util/XboxAuthNet/PlatformManager.cs ================================================ using System.IO; using Microsoft.Web.WebView2.Core; using XboxAuthNet.OAuth.CodeFlow; namespace XboxAuthNet; public class PlatformManager { private static PlatformManager? _currentPlatformInstance; public static PlatformManager CurrentPlatform => _currentPlatformInstance ??= new PlatformManager(); public IWebUI CreateWebUI(WebUIOptions uiOptions) { IWebUI? ui = null; if (!XboxAuthNet.Platforms.WinForm.WebView2WebUI.IsWebView2Available()) throw new Microsoft.Web.WebView2.Core.WebView2RuntimeNotFoundException(); ui = new XboxAuthNet.Platforms.WinForm.WebView2WebUI(uiOptions); if (ui == null) throw new PlatformNotSupportedException("Current platform does not support to provide default WebUI."); return ui; } } ================================================ FILE: XAU/Util/XboxAuthNet/Platforms/WinForm/StaTaskScheduler.cs ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. #if !NETSTANDARD using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace XboxAuthNet.Platforms.WinForm { // This IDisposable class doe not need to implement Dispose method in standard way, because it is sealed. // 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. /// Provides a scheduler that uses STA threads. #if NET5_WIN [System.Runtime.Versioning.SupportedOSPlatform("windows")] #endif internal sealed class StaTaskScheduler : TaskScheduler, IDisposable { /// The STA threads used by the scheduler. private readonly List _threads; /// Stores the queued tasks to be executed by our pool of STA threads. private BlockingCollection _tasks; /// Initializes a new instance of the StaTaskScheduler class with the specified concurrency level. /// The number of threads that should be created and used by this scheduler. public StaTaskScheduler(int numberOfThreads) { // Validate arguments if (numberOfThreads < 1) { throw new ArgumentOutOfRangeException(nameof(numberOfThreads)); } // Initialize the tasks collection _tasks = new BlockingCollection(); // Create the threads to be used by this scheduler _threads = Enumerable.Range(0, numberOfThreads).Select(i => { var thread = new Thread(() => { // Continually get the next task and try to execute it. // This will continue until the scheduler is disposed and no more tasks remain. foreach (var t in _tasks.GetConsumingEnumerable()) { TryExecuteTask(t); } }) { IsBackground = true }; thread.SetApartmentState(ApartmentState.STA); return thread; }).ToList(); // Start all of the threads _threads.ForEach(t => t.Start()); } /// Gets the maximum concurrency level supported by this scheduler. public override int MaximumConcurrencyLevel { get { return _threads.Count; } } /// /// Cleans up the scheduler by indicating that no more tasks will be queued. /// This method blocks until all threads successfully shutdown. /// public void Dispose() { if (_tasks != null) { // Indicate that no new tasks will be coming in _tasks.CompleteAdding(); // Wait for all threads to finish processing tasks foreach (var thread in _threads) { thread.Join(); } // Cleanup _tasks.Dispose(); _tasks = null!; } } /// Queues a Task to be executed by this scheduler. /// The task to be executed. protected override void QueueTask(Task task) { // Push it into the blocking collection of tasks _tasks.Add(task); } /// Provides a list of the scheduled tasks for the debugger to consume. /// An enumerable of all tasks currently scheduled. protected override IEnumerable GetScheduledTasks() { // Serialize the contents of the blocking collection of tasks for the debugger return _tasks.ToArray(); } /// Determines whether a Task may be inlined. /// The task to be executed. /// Whether the task was previously queued. /// true if the task was successfully inlined; otherwise, false. protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { // Try to inline if the current thread is STA return Thread.CurrentThread.GetApartmentState() == ApartmentState.STA && TryExecuteTask(task); } } } #endif ================================================ FILE: XAU/Util/XboxAuthNet/Platforms/WinForm/UIThreadHelper.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; namespace XboxAuthNet.Platforms.WinForm { internal static class UIThreadHelper { public static async Task InvokeUIActionOnSafeThread( Action action, SynchronizationContext? synchronizationContext, CancellationToken cancellationToken) { if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) { await invokeWithinMtaThread(action, synchronizationContext, cancellationToken); } else { action.Invoke(); } } private static async Task invokeWithinMtaThread( Action action, SynchronizationContext? synchronizationContext, CancellationToken cancellationToken) { if (synchronizationContext != null) { var actionWithTcs = new Action((tcs) => { try { action.Invoke(); ((TaskCompletionSource)tcs!).TrySetResult(null); } catch (Exception e) { // Need to catch the exception here and put on the TCS which is the task we are waiting on so that // the exception comming out of Authenticate is correctly thrown. ((TaskCompletionSource)tcs!).TrySetException(e); } }); var tcs2 = new TaskCompletionSource(); synchronizationContext.Post( new SendOrPostCallback(actionWithTcs), tcs2); await tcs2.Task.ConfigureAwait(false); } else { using (var staTaskScheduler = new StaTaskScheduler(1)) { try { Task.Factory.StartNew( action, cancellationToken, TaskCreationOptions.None, staTaskScheduler).Wait(); } catch (AggregateException ae) { // Any exception thrown as a result of running task will cause AggregateException to be thrown with // actual exception as inner. Exception innerException = ae.InnerExceptions[0]; // In MTA case, AggregateException is two layer deep, so checking the InnerException for that. if (innerException is AggregateException) { innerException = ((AggregateException)innerException).InnerExceptions[0]; } throw innerException; } } } } } } ================================================ FILE: XAU/Util/XboxAuthNet/Platforms/WinForm/WebView2WebUI.cs ================================================ using Microsoft.Web.WebView2.Core; using XboxAuthNet.OAuth.CodeFlow; namespace XboxAuthNet.Platforms.WinForm; #if NET5_WIN [System.Runtime.Versioning.SupportedOSPlatform("windows7")] #endif internal class WebView2WebUI : IWebUI { private readonly object? _parent; private readonly SynchronizationContext? _synchronizationContext; public WebView2WebUI(WebUIOptions options) { _parent = options.ParentObject; _synchronizationContext = options.SynchronizationContext; } public async Task DisplayDialogAndInterceptUri( Uri uri, ICodeFlowUrlChecker uriChecker, CancellationToken cancellationToken) { WpfWindowWrapper window = new WpfWindowWrapper(); CodeFlowAuthorizationResult result = new CodeFlowAuthorizationResult(); await UIThreadHelper.InvokeUIActionOnSafeThread(() => { using (var form = new WinFormsPanelWithWebView2(window.Handle)) { result = form.DisplayDialogAndInterceptUri(uri, uriChecker, cancellationToken); } }, _synchronizationContext, cancellationToken); return result; } public async Task DisplayDialogAndNavigateUri(Uri uri, CancellationToken cancellationToken) { WpfWindowWrapper window = new WpfWindowWrapper(); await UIThreadHelper.InvokeUIActionOnSafeThread(() => { using (var form = new WinFormsPanelWithWebView2(window.Handle)) { form.DisplayDialogAndNavigateUri(uri, cancellationToken); } }, _synchronizationContext, cancellationToken); } public static bool IsWebView2Available() { try { string wv2Version = CoreWebView2Environment.GetAvailableBrowserVersionString(); return !string.IsNullOrEmpty(wv2Version); } catch (WebView2RuntimeNotFoundException) { return false; } catch (Exception ex) when (ex is BadImageFormatException || ex is DllNotFoundException) { return false; //throw new MsalClientException(MsalError.WebView2LoaderNotFound, MsalErrorMessage.WebView2LoaderNotFound, ex); } } public class WpfWindowWrapper : System.Windows.Forms.IWin32Window { public WpfWindowWrapper() { Handle = new System.Windows.Interop.WindowInteropHelper(System.Windows.Application.Current.MainWindow).Handle; } public IntPtr Handle { get; private set; } } } ================================================ FILE: XAU/Util/XboxAuthNet/Platforms/WinForm/Win32Window.cs ================================================ // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/main/src/client/Microsoft.Identity.Client/Platforms/Features/WebView2WebUi/Win32Window.cs // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; using System.Windows.Forms; namespace Microsoft.Identity.Client.Platforms.Features.WebView2WebUi { internal class Win32Window : IWin32Window { public Win32Window(IntPtr handle) { Handle = handle; } public IntPtr Handle { get; } } } ================================================ FILE: XAU/Util/XboxAuthNet/Platforms/WinForm/WinFormsPanelWithWebView2.cs ================================================ // This code is from MSAL.NET // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/main/src/client/Microsoft.Identity.Client/Platforms/Features/WebView2WebUi/WinFormsPanelWithWebView2.cs // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Microsoft.Identity.Client.Platforms.Features.DesktopOs; using Microsoft.Identity.Client.Platforms.Features.WebView2WebUi; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.WinForms; using XboxAuthNet.OAuth.CodeFlow; using Application = System.Windows.Forms.Application; namespace XboxAuthNet.Platforms.WinForm; internal class WinFormsPanelWithWebView2 : Form { private const int UIWidth = 566; private WebView2 _webView2; private const string WebView2UserDataFolder = "%UserProfile%/.msal/webview2/data"; private ICodeFlowUrlChecker? _uriChecker; private CodeFlowAuthorizationResult _authCode; private IWin32Window? _ownerWindow; public WinFormsPanelWithWebView2( object? ownerWindow) { if (ownerWindow == null) { _ownerWindow = null; } else if (ownerWindow is IWin32Window) { _ownerWindow = (IWin32Window)ownerWindow; } else if (ownerWindow is IntPtr ptr && ptr != IntPtr.Zero) { _ownerWindow = new Win32Window(ptr); } else { throw new ArgumentException("Invalid owner window type. Expected types are IWin32Window or IntPtr (for window handle)."); } InitializeComponent(); CoreWebView2Environment.GetAvailableBrowserVersionString(); _webView2!.CreationProperties = new CoreWebView2CreationProperties() { UserDataFolder = Environment.ExpandEnvironmentVariables(WebView2UserDataFolder) }; } public CodeFlowAuthorizationResult DisplayDialogAndInterceptUri( Uri uri, ICodeFlowUrlChecker uriChecker, CancellationToken cancellationToken) { this._uriChecker = uriChecker; _webView2.CoreWebView2InitializationCompleted += WebView2Control_CoreWebView2InitializationCompleted; _webView2.NavigationStarting += WebView2Control_NavigationStarting; // Starts the navigation _webView2.Source = uri; DisplayDialog(cancellationToken); if (_authCode.IsEmpty) throw new InvalidOperationException("_authCode was empty"); return _authCode; } public void DisplayDialogAndNavigateUri(Uri uri, CancellationToken cancellationToken) { _webView2.CoreWebView2InitializationCompleted += WebView2Control_CoreWebView2InitializationCompleted; // Starts the navigation _webView2.Source = uri; using (cancellationToken.Register(CloseIfOpen)) { InvokeHandlingOwnerWindow(() => ShowDialog(_ownerWindow)); cancellationToken.ThrowIfCancellationRequested(); } } private void DisplayDialog(CancellationToken cancellationToken) { DialogResult uiResult = DialogResult.None; using (cancellationToken.Register(CloseIfOpen)) { InvokeHandlingOwnerWindow(() => uiResult = ShowDialog(_ownerWindow)); cancellationToken.ThrowIfCancellationRequested(); } switch (uiResult) { case DialogResult.OK: break; case DialogResult.Cancel: throw new AuthCodeException(null, "User canceled authentication. "); default: throw new InvalidOperationException( "WebView2 returned an unexpected result: " + uiResult); } } private void CloseIfOpen() { if (Application.OpenForms.OfType().Any()) { InvokeOnly(Close); } } private void PlaceOnTop(object? sender, EventArgs e) { // If we don't have an owner we need to make sure that the pop up browser // window is on top of other windows. Activating the window will accomplish this. if (null == Owner) { Activate(); } } /// /// Some calls need to be made on the UI thread and this is the central place to check if we have an owner /// window and if so, ensure we invoke on that proper thread. /// /// private void InvokeHandlingOwnerWindow(Action action) { // We only support WindowsForms (since our dialog is Win Forms based) if (_ownerWindow != null && _ownerWindow is Control winFormsControl) { winFormsControl.Invoke(action); } else { action(); } } /// /// 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. /// /// private void InvokeOnly(Action action) { if (InvokeRequired) { this.Invoke(action); } else { action(); } } private void InitializeComponent() { InvokeHandlingOwnerWindow(() => { Screen screen = (_ownerWindow != null) ? Screen.FromHandle(_ownerWindow.Handle) : Screen.PrimaryScreen; // Window height is set to 70% of the screen height. int uiHeight = (int)(Math.Max(screen.WorkingArea.Height, 160) * 70.0 / WindowsDpiHelper.ZoomPercent); var webBrowserPanel = new Panel(); webBrowserPanel.SuspendLayout(); SuspendLayout(); // webBrowser _webView2 = new WebView2(); _webView2.Dock = DockStyle.Fill; _webView2.Location = new System.Drawing.Point(0, 25); _webView2.MinimumSize = new System.Drawing.Size(20, 20); _webView2.Name = "WebView2"; _webView2.Size = new System.Drawing.Size(UIWidth, 565); _webView2.TabIndex = 1; // webBrowserPanel webBrowserPanel.Controls.Add(_webView2); webBrowserPanel.Dock = DockStyle.Fill; webBrowserPanel.BorderStyle = BorderStyle.None; webBrowserPanel.Location = new System.Drawing.Point(0, 0); webBrowserPanel.Name = "webBrowserPanel"; webBrowserPanel.Size = new System.Drawing.Size(UIWidth, uiHeight); webBrowserPanel.TabIndex = 2; // BrowserAuthenticationWindow AutoScaleDimensions = new SizeF(6, 13); AutoScaleMode = AutoScaleMode.Font; ClientSize = new System.Drawing.Size(UIWidth, uiHeight); Controls.Add(webBrowserPanel); FormBorderStyle = FormBorderStyle.FixedSingle; Name = "BrowserAuthenticationWindow"; // Move the window to the center of the parent window only if owner window is set. StartPosition = (_ownerWindow != null) ? FormStartPosition.CenterParent : FormStartPosition.CenterScreen; Text = string.Empty; ShowIcon = false; MaximizeBox = false; MinimizeBox = false; // If we don't have an owner we need to make sure that the pop up browser // window is in the task bar so that it can be selected with the mouse. ShowInTaskbar = null == _ownerWindow; webBrowserPanel.ResumeLayout(false); ResumeLayout(false); }); this.Shown += PlaceOnTop; } private void WebView2Control_NavigationStarting(object? sender, CoreWebView2NavigationStartingEventArgs e) { if (CheckForEndUrl(new Uri(e.Uri))) { // _logger.Verbose("[WebView2Control] Redirect URI reached. Stopping the interactive view"); e.Cancel = true; } else { // _logger.Verbose("[WebView2Control] Navigating to " + e.Uri); } } private bool CheckForEndUrl(Uri url) { if (_uriChecker == null) throw new InvalidOperationException("_uriChecker was null"); var result = _uriChecker.GetAuthCodeResult(url); if (!result.IsEmpty) { // This should close the dialog DialogResult = DialogResult.OK; _authCode = result; _uriChecker = null; } return !result.IsEmpty; } private void WebView2Control_CoreWebView2InitializationCompleted(object? sender, CoreWebView2InitializationCompletedEventArgs e) { //_logger.Verbose("[WebView2Control] CoreWebView2InitializationCompleted "); _webView2.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; _webView2.CoreWebView2.Settings.AreDevToolsEnabled = false; _webView2.CoreWebView2.Settings.AreHostObjectsAllowed = false; _webView2.CoreWebView2.Settings.IsScriptEnabled = true; _webView2.CoreWebView2.Settings.IsZoomControlEnabled = false; _webView2.CoreWebView2.Settings.IsStatusBarEnabled = true; _webView2.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = false; _webView2.CoreWebView2.DocumentTitleChanged += CoreWebView2_DocumentTitleChanged; } private void CoreWebView2_DocumentTitleChanged(object? sender, object e) { Text = _webView2.CoreWebView2.DocumentTitle ?? ""; } } ================================================ FILE: XAU/Util/XboxAuthNet/Platforms/WinForm/WindowsDpiHelper.cs ================================================ // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/main/src/client/Microsoft.Identity.Client/Platforms/Features/DesktopOS/WindowsNativeDpiHelper.cs // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; using System.Runtime.InteropServices; namespace Microsoft.Identity.Client.Platforms.Features.DesktopOs { internal static class WindowsDpiHelper { static WindowsDpiHelper() { const double DefaultDpi = 96.0; const int LOGPIXELSX = 88; const int LOGPIXELSY = 90; double deviceDpiX; double deviceDpiY; IntPtr dC = GetDC(IntPtr.Zero); if (dC != IntPtr.Zero) { deviceDpiX = GetDeviceCaps(dC, LOGPIXELSX); deviceDpiY = GetDeviceCaps(dC, LOGPIXELSY); ReleaseDC(IntPtr.Zero, dC); } else { deviceDpiX = DefaultDpi; deviceDpiY = DefaultDpi; } int zoomPercentX = (int)(100 * (deviceDpiX / DefaultDpi)); int zoomPercentY = (int)(100 * (deviceDpiY / DefaultDpi)); ZoomPercent = Math.Min(zoomPercentX, zoomPercentY); } /// /// public static int ZoomPercent { get; } [DllImport("User32.dll", CallingConvention = CallingConvention.StdCall, ExactSpelling = true)] internal static extern IntPtr GetDC(IntPtr hWnd); [DllImport("User32.dll", CallingConvention = CallingConvention.StdCall, ExactSpelling = true)] internal static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); [DllImport("Gdi32.dll", CallingConvention = CallingConvention.StdCall, ExactSpelling = true)] internal static extern int GetDeviceCaps(IntPtr hdc, int nIndex); [DllImport("User32.dll", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] internal static extern bool IsProcessDPIAware(); } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxAuthNet.csproj ================================================  netstandard2.0 net472 net5.0-windows $(TargetFrameworkNet5Win);$(TargetFrameworkNetStandard);$(TargetFrameworkNetFramework) true 10.0 true enable true enable 3.0.2 Xbox Live authentication for .NET MIT MIT https://github.com/AlphaBs/XboxAuthNet https://github.com/AlphaBs/XboxAuthNet git ksi123456ab xbox live login auth authentication <_Parameter1>XboxAuthNet.Test all runtime; build; native; contentfiles; analyzers; buildtransitive false true $(DefineConstants);ENABLE_WEBVIEW2 true $(DefineConstants);ENABLE_WEBVIEW2;NET5_WIN ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Crypto/ECDCertificatePopCryptoProvider.cs ================================================ using System.Security.Cryptography; namespace XboxAuthNet.XboxLive.Crypto { // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/main/tests/Microsoft.Identity.Test.Common/Core/Helpers/ECDCertificatePopCryptoProvider.cs#L47 public class ECDCertificatePopCryptoProvider : IPopCryptoProvider { private object? _proofKey; public object ProofKey => _proofKey ??= generateNewProofKey(); private ECDsa _signer; public ECDCertificatePopCryptoProvider() { var ecCurve = ECCurve.NamedCurves.nistP256; _signer = ECDsa.Create(ecCurve); } private object generateNewProofKey() { var parameters = _signer.ExportParameters(false); return new { kty = "EC", x = parameters.Q.X != null ? Base64UrlHelper.Encode(parameters.Q.X) : null, y = parameters.Q.Y != null ? Base64UrlHelper.Encode(parameters.Q.Y) : null, crv = "P-256", alg = "ES256", use = "sig" }; } public byte[] Sign(byte[] data) { return _signer.SignData(data, HashAlgorithmName.SHA256); } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Crypto/IPopCryptoProvider.cs ================================================ namespace XboxAuthNet.XboxLive.Crypto { public interface IPopCryptoProvider { object ProofKey { get; } byte[] Sign(byte[] data); } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Crypto/IXboxRequestSigner.cs ================================================ namespace XboxAuthNet.XboxLive.Crypto { public interface IXboxRequestSigner { object ProofKey { get; } string SignRequest(string reqUri, string token, string body); } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Crypto/XboxRequestSigner.cs ================================================ using System; using System.Text; namespace XboxAuthNet.XboxLive.Crypto { public class XboxRequestSigner : IXboxRequestSigner { private readonly IPopCryptoProvider _signer; public XboxRequestSigner(IPopCryptoProvider signer) { this._signer = signer; } public object ProofKey => _signer.ProofKey; public string SignRequest(string reqUri, string token, string body) { var timestamp = getWindowsTimestamp(); var data = generatePayload(timestamp, reqUri, token, body); var signature = sign(timestamp, data); return Convert.ToBase64String(signature); } private byte[] generatePayload(ulong windowsTimestamp, string uri, string token, string payload) { var pathAndQuery = new Uri(uri).PathAndQuery; var allocSize = 4 + 1 + 8 + 1 + 4 + 1 + pathAndQuery.Length + 1 + token.Length + 1 + payload.Length + 1; var bytes = new byte[allocSize]; var policyVersion = BitConverter.GetBytes((int)1); if (BitConverter.IsLittleEndian) Array.Reverse(policyVersion); Array.Copy(policyVersion, 0, bytes, 0, 4); var windowsTimestampBytes = BitConverter.GetBytes(windowsTimestamp); if (BitConverter.IsLittleEndian) Array.Reverse(windowsTimestampBytes); Array.Copy(windowsTimestampBytes, 0, bytes, 5, 8); var strs = $"POST\0" + $"{pathAndQuery}\0" + $"{token}\0" + $"{payload}\0"; var strsBytes = Encoding.ASCII.GetBytes(strs); Array.Copy(strsBytes, 0, bytes, 14, strsBytes.Length); return bytes; } private byte[] sign(ulong windowsTimestamp, byte[] bytes) { var signature = _signer.Sign(bytes); var policyVersion = BitConverter.GetBytes((int)1); if (BitConverter.IsLittleEndian) Array.Reverse(policyVersion); var windowsTimestampBytes = BitConverter.GetBytes(windowsTimestamp); if (BitConverter.IsLittleEndian) Array.Reverse(windowsTimestampBytes); var header = new byte[signature.Length + 12]; Array.Copy(policyVersion, 0, header, 0, 4); Array.Copy(windowsTimestampBytes, 0, header, 4, 8); Array.Copy(signature, 0, header, 12, signature.Length); return header; } private ulong getWindowsTimestamp() { var unixTimestamp = (ulong)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); ulong windowsTimestamp = (unixTimestamp + 11644473600u) * 10000000u; return windowsTimestamp; } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Requests/AbstractXboxAuthRequest.cs ================================================ using System.Net.Http; using XboxAuthNet.XboxLive.Responses; namespace XboxAuthNet.XboxLive.Requests; public abstract class AbstractXboxAuthRequest { public XboxAuthResponseHandler? ResponseHandler { get; set; } = new(); public string? ContractVersion { get; set; } protected abstract HttpRequestMessage BuildRequest(); public async Task Send(HttpClient httpClient) { if (ResponseHandler == null) throw new InvalidOperationException("ResponseHandler was null"); var request = BuildRequest(); request.Headers.Add("x-xbl-contract-version", ContractVersion ?? ""); var response = await httpClient.SendAsync(request); return await ResponseHandler.HandleResponse(response); } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Requests/AbstractXboxSignedAuthRequest.cs ================================================ using System.Net.Http; using System.Text; using System.Text.Json; using XboxAuthNet.XboxLive.Crypto; using XboxAuthNet.XboxLive.Responses; namespace XboxAuthNet.XboxLive.Requests; public abstract class AbstractXboxSignedAuthRequest { public XboxAuthResponseHandler ResponseHandler { get; set; } = new(); protected abstract string RequestUrl { get; } protected virtual string Token { get; } = ""; public async Task Send(HttpClient httpClient, IXboxRequestSigner signer) { if (ResponseHandler == null) throw new InvalidOperationException("ResponseHandler was null"); var request = buildRequest(signer); var response = await httpClient.SendAsync(request); return await ResponseHandler.HandleResponse(response); } private HttpRequestMessage buildRequest(IXboxRequestSigner signer) { var body = BuildBody(signer.ProofKey); var bodyStr = JsonSerializer.Serialize(body); var req = new HttpRequestMessage { RequestUri = new Uri(RequestUrl), Method = HttpMethod.Post, Content = new StringContent(bodyStr, Encoding.UTF8, "application/json") }; var signature = signer.SignRequest(RequestUrl, Token, bodyStr); req.Headers.Add("Signature", signature); CommonRequestHeaders.AddDefaultHeaders(req); return req; } protected abstract object BuildBody(object proofKey); } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Requests/CommonRequestHeaders.cs ================================================ using System.Net.Http; namespace XboxAuthNet.XboxLive.Requests; internal class CommonRequestHeaders { public static void AddDefaultHeaders(HttpRequestMessage request) { request.Headers.Add("Accept", "application/json"); request.Headers.TryAddWithoutValidation("User-Agent", HttpHelper.UserAgent); request.Headers.Add("Accept-Language", "en-US"); request.Headers.Add("Cache-Control", "no-store, must-revalidate, no-cache"); } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Requests/XboxDeviceTokenRequest.cs ================================================ using System; using System.Net.Http; using System.Threading.Tasks; using XboxAuthNet.XboxLive.Crypto; using XboxAuthNet.XboxLive.Responses; namespace XboxAuthNet.XboxLive.Requests { public class XboxDeviceTokenRequest : AbstractXboxSignedAuthRequest { public string? Id { get; set; } public string? SerialNumber { get; set; } public string? DeviceType { get; set; } public string? DeviceVersion { get; set; } public string? RelyingParty { get; set; } = XboxAuthConstants.XboxAuthRelyingParty; protected override string RequestUrl => "https://device.auth.xboxlive.com/device/authenticate"; protected override object BuildBody(object proofKey) { if (string.IsNullOrEmpty(DeviceType)) throw new InvalidOperationException("DeviceType was null"); if (string.IsNullOrEmpty(DeviceVersion)) throw new InvalidOperationException("DeviceVersion was null"); if (string.IsNullOrEmpty(RelyingParty)) throw new InvalidOperationException("RelyingParty was null"); var id = this.Id ?? nextUUID(); var serialNumber = this.SerialNumber ?? nextUUID(); return new { Properties = new { AuthMethod = "ProofOfPossession", Id = "{" + id + "}", DeviceType = DeviceType, SerialNumber = "{" + serialNumber + "}", Version = DeviceVersion, ProofKey = proofKey }, RelyingParty = RelyingParty, TokenType = "JWT" }; } private string nextUUID() { return Guid.NewGuid().ToString(); } public Task Send(HttpClient httpClient, IXboxRequestSigner signer) { return Send(httpClient, signer); } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Requests/XboxSignedUserTokenRequest.cs ================================================ using System; using System.Net.Http; using System.Threading.Tasks; using XboxAuthNet.XboxLive.Crypto; using XboxAuthNet.XboxLive.Responses; namespace XboxAuthNet.XboxLive.Requests { public class XboxSignedUserTokenRequest : AbstractXboxSignedAuthRequest { public string? RelyingParty { get; set; } = XboxAuthConstants.XboxAuthRelyingParty; public string? TokenPrefix { get; set; } = XboxAuthConstants.XboxTokenPrefix; public string? AccessToken { get; set; } protected override string RequestUrl => "https://user.auth.xboxlive.com/user/authenticate"; protected override object BuildBody(object proofKey) { if (string.IsNullOrEmpty(AccessToken)) throw new InvalidOperationException("AccessToken was null"); if (string.IsNullOrEmpty(RelyingParty)) throw new InvalidOperationException("RelyingParty was null"); return new { RelyingParty = RelyingParty, TokenType = "JWT", Properties = new { AuthMethod = "RPS", SiteName = "user.auth.xboxlive.com", RpsTicket = TokenPrefix + AccessToken } }; } public Task Send(HttpClient httpClient, IXboxRequestSigner signer) { return Send(httpClient, signer); } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Requests/XboxSignedXstsRequest.cs ================================================ using System.Threading.Tasks; using System.Net.Http; using XboxAuthNet.XboxLive.Crypto; using XboxAuthNet.XboxLive.Responses; namespace XboxAuthNet.XboxLive.Requests { public class XboxSignedXstsRequest : AbstractXboxSignedAuthRequest { public string? UserToken { get; set; } public string? DeviceToken { get; set; } public string? TitleToken { get; set; } public string? RelyingParty { get; set; } = XboxAuthConstants.XboxLiveRelyingParty; public string[]? OptionalDisplayClaims { get; set; } protected override string RequestUrl => "https://xsts.auth.xboxlive.com/xsts/authorize"; protected override object BuildBody(object proofKey) { if (string.IsNullOrEmpty(UserToken)) throw new InvalidOperationException("UserToken was null"); if (string.IsNullOrEmpty(RelyingParty)) throw new InvalidOperationException("RelyingParty was null"); return new { RelyingParty = RelyingParty, TokenType = "JWT", Properties = new { UserTokens = new string[] { UserToken }, DeviceToken = DeviceToken, TitleToken = TitleToken, OptionalDisplayClaims = OptionalDisplayClaims, SandboxId = "RETAIL", ProofKey = proofKey } }; } public Task Send(HttpClient httpClient, IXboxRequestSigner signer) { return Send(httpClient, signer); } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Requests/XboxSisuAuthRequest.cs ================================================ using System; using System.Threading.Tasks; using System.Net.Http; using XboxAuthNet.XboxLive.Responses; using XboxAuthNet.XboxLive.Crypto; namespace XboxAuthNet.XboxLive.Requests { public class XboxSisuAuthRequest : AbstractXboxSignedAuthRequest { public string? TokenPrefix { get; set; } = XboxAuthConstants.XboxTokenPrefix; public string? AccessToken { get; set; } public string? RelyingParty { get; set; } = XboxAuthConstants.XboxLiveRelyingParty; public string? ClientId { get; set; } public string? DeviceToken { get; set; } protected override string RequestUrl => "https://sisu.xboxlive.com/authorize"; protected override object BuildBody(object proofKey) { if (string.IsNullOrEmpty(AccessToken)) throw new InvalidOperationException("AccessToken was null"); if (string.IsNullOrEmpty(RelyingParty)) throw new InvalidOperationException("RelyingParty was null"); return new { AccessToken = TokenPrefix + AccessToken, AppId = ClientId, DeviceToken = DeviceToken, Sandbox = "RETAIL", UseModernGamertag = true, SiteName = "user.auth.xboxlive.com", RelyingParty = RelyingParty, ProofKey = proofKey }; } public Task Send(HttpClient httpClient, IXboxRequestSigner signer) { return Send(httpClient, signer); } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Requests/XboxTitleTokenRequest.cs ================================================ using System; using System.Net.Http; using System.Threading.Tasks; using XboxAuthNet.XboxLive.Crypto; using XboxAuthNet.XboxLive.Responses; namespace XboxAuthNet.XboxLive.Requests { public class XboxTitleTokenRequest : AbstractXboxSignedAuthRequest { public string? AccessToken { get; set; } public string? DeviceToken { get; set; } public string? TokenPrefix { get; set; } = XboxAuthConstants.XboxTokenPrefix; public string? RelyingParty { get; set; } = XboxAuthConstants.XboxAuthRelyingParty; protected override string RequestUrl => "https://title.auth.xboxlive.com/title/authenticate"; protected override object BuildBody(object proofKey) { if (string.IsNullOrEmpty(AccessToken)) throw new InvalidOperationException("AccessToken was null"); if (string.IsNullOrEmpty(RelyingParty)) throw new InvalidOperationException("RelyingParty was null"); return new { Properties = new { AuthMethod = "RPS", DeviceToken = DeviceToken, RpsTicket = TokenPrefix + AccessToken, SiteName = "user.auth.xboxlive.com", ProofKey = proofKey, }, RelyingParty = RelyingParty, TokenType = "JWT" }; } public Task Send(HttpClient httpClient, IXboxRequestSigner signer) { return Send(httpClient, signer); } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Requests/XboxUserTokenRequest.cs ================================================ using System; using System.Net.Http; using System.Threading.Tasks; using XboxAuthNet.XboxLive.Responses; namespace XboxAuthNet.XboxLive.Requests { public class XboxUserTokenRequest : AbstractXboxAuthRequest { public const string UserAuthenticateUrl = "https://user.auth.xboxlive.com/user/authenticate"; public XboxUserTokenRequest() { ContractVersion = "0"; RelyingParty = XboxAuthConstants.XboxAuthRelyingParty; } public string? AccessToken { get; set; } public string? RelyingParty { get; set; } protected override HttpRequestMessage BuildRequest() { if (string.IsNullOrEmpty(AccessToken)) throw new InvalidOperationException("AccessToken was null"); var req = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new Uri(UserAuthenticateUrl), Content = HttpHelper.CreateJsonContent(new { RelyingParty = RelyingParty, TokenType = "JWT", Properties = new { AuthMethod = "RPS", SiteName = "user.auth.xboxlive.com", RpsTicket = AccessToken } }) }; CommonRequestHeaders.AddDefaultHeaders(req); return req; } public Task Send(HttpClient httpClient) { return Send(httpClient); } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Requests/XboxXstsRequest.cs ================================================ using System; using System.Net.Http; using System.Threading.Tasks; using XboxAuthNet.XboxLive.Responses; namespace XboxAuthNet.XboxLive.Requests { public class XboxXstsRequest : AbstractXboxAuthRequest { public const string XstsAuthorizeUrl = "https://xsts.auth.xboxlive.com/xsts/authorize"; public XboxXstsRequest() { RelyingParty = XboxAuthConstants.XboxLiveRelyingParty; ContractVersion = "1"; } public string? UserToken { get; set; } public string? RelyingParty { get; set; } public string? DeviceToken { get; set; } public string? TitleToken { get; set; } public string[]? OptionalDisplayClaims { get; set; } protected override HttpRequestMessage BuildRequest() { if (string.IsNullOrEmpty(UserToken)) throw new InvalidOperationException("UserToken was null"); if (string.IsNullOrEmpty(RelyingParty)) throw new InvalidOperationException("RelyingParty was null"); var req = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new Uri(XstsAuthorizeUrl), Content = HttpHelper.CreateJsonContent(new { RelyingParty = RelyingParty, TokenType = "JWT", Properties = new { UserTokens = new string[] { UserToken }, DeviceToken = DeviceToken, TitleToken = TitleToken, OptionalDisplayClaims = OptionalDisplayClaims, SandboxId = "RETAIL" } }), }; CommonRequestHeaders.AddDefaultHeaders(req); return req; } public Task Send(HttpClient httpClient) { return Send(httpClient); } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Responses/XErrJsonConverter.cs ================================================ using System; using System.Text.Json; using System.Text.Json.Serialization; namespace XboxAuthNet.XboxLive.Responses { public class XErrJsonConverter : JsonConverter { public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { string? value; if (reader.TokenType == JsonTokenType.String) value = reader.GetString(); else if (reader.TokenType == JsonTokenType.Number) value = reader.GetInt64().ToString(); else throw new JsonException(); return ErrorHelper.TryConvertToHexErrorCode(value); } public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) { writer.WriteStringValue(value); } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Responses/XboxAuthResponse.cs ================================================ using System; using System.Text.Json.Serialization; namespace XboxAuthNet.XboxLive.Responses { public class XboxAuthResponse { [JsonPropertyName("DisplayClaims")] [JsonConverter(typeof(XboxAuthXuiClaimsJsonConverter))] public XboxAuthXuiClaims? XuiClaims { get; set; } [JsonPropertyName("IssueInstant")] public string? IssueInstant { get; set; } [JsonPropertyName("Token")] public string? Token { get; set; } [JsonPropertyName("NotAfter")] public string? ExpireOn { get; set; } /// /// checks token is not null and not empty, checks token is not expired /// /// public bool Validate() { if (string.IsNullOrEmpty(ExpireOn)) return false; if (DateTime.Parse(ExpireOn) < DateTime.UtcNow) return false; if (string.IsNullOrEmpty(Token)) return false; return true; } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Responses/XboxAuthResponseHandler.cs ================================================ using System; using System.Threading.Tasks; using System.Net.Http; using System.Text.Json; namespace XboxAuthNet.XboxLive.Responses { public class XboxAuthResponseHandler { public async Task HandleResponse(HttpResponseMessage res) { var resBody = await res.Content.ReadAsStringAsync() .ConfigureAwait(false); try { res.EnsureSuccessStatusCode(); return JsonSerializer.Deserialize(resBody) ?? throw new JsonException(); } catch (Exception ex) when ( ex is JsonException || ex is HttpRequestException) { try { throw XboxAuthException.FromResponseBody(resBody, (int)res.StatusCode); } catch (FormatException) { try { throw XboxAuthException.FromResponseHeaders(res.Headers, (int)res.StatusCode); } catch (FormatException) { throw new XboxAuthException($"{(int)res.StatusCode}: {res.ReasonPhrase}", (int)res.StatusCode); } } } } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Responses/XboxAuthXuiClaims.cs ================================================ using System.Text.Json.Serialization; namespace XboxAuthNet.XboxLive.Responses { // https://github.com/OpenXbox/xbox-webapi-csharp/blob/master/XboxWebApi/Authentication/Model/XboxUserInformation.cs public class XboxAuthXuiClaims { [JsonPropertyName(XboxAuthXuiClaimNames.Gamertag)] public string? Gamertag { get; set; } [JsonPropertyName("mgt")] public string? ModernGamertag { get; set; } [JsonPropertyName("umg")] public string? UniqueModernGamertag { get; set; } [JsonPropertyName("mgs")] public string? ModernGamertagSuffix { get; set; } [JsonPropertyName(XboxAuthXuiClaimNames.XboxUserId)] public string? XboxUserId { get; set; } [JsonPropertyName(XboxAuthXuiClaimNames.UserHash)] public string? UserHash { get; set; } [JsonPropertyName(XboxAuthXuiClaimNames.AgeGroup)] public string? AgeGroup { get; set; } [JsonPropertyName(XboxAuthXuiClaimNames.UserSettingsRestrictions)] public string? UserSettingsRestrictions { get; set; } [JsonPropertyName(XboxAuthXuiClaimNames.UserTitleRestrictions)] public string? UserTitleRestrictions { get; set; } [JsonPropertyName(XboxAuthXuiClaimNames.Privileges)] public string? Privileges { get; set; } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Responses/XboxAuthXuiClaimsJsonConverter.cs ================================================ using System; using System.Text.Json; using System.Text.Json.Serialization; namespace XboxAuthNet.XboxLive.Responses { public class XboxAuthXuiClaimsJsonConverter : JsonConverter { public override XboxAuthXuiClaims? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) return null; XboxAuthXuiClaims? claims = null; while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) break; if (reader.TokenType != JsonTokenType.PropertyName) throw new JsonException(); var propName = reader.GetString(); if (propName == "xui") { reader.Read(); claims = readXui(ref reader, options); } else reader.Skip(); } return claims; } private XboxAuthXuiClaims? readXui(ref Utf8JsonReader reader, JsonSerializerOptions options) { XboxAuthXuiClaims? claims = null; if (reader.TokenType == JsonTokenType.StartArray) { while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndArray) break; if (claims == null && reader.TokenType == JsonTokenType.StartObject) claims = JsonSerializer.Deserialize(ref reader, options); else reader.Skip(); } } else if (reader.TokenType == JsonTokenType.StartObject) { claims = JsonSerializer.Deserialize(ref reader, options); } return claims; } public override void Write(Utf8JsonWriter writer, XboxAuthXuiClaims value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WritePropertyName("xui"); writer.WriteStartArray(); JsonSerializer.Serialize(writer, value, options); writer.WriteEndArray(); writer.WriteEndObject(); } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Responses/XboxErrorResponse.cs ================================================ using System.Text.Json.Serialization; namespace XboxAuthNet.XboxLive.Responses { public class XboxErrorResponse { [JsonPropertyName("XErr")] [JsonConverter(typeof(XErrJsonConverter))] public string? XErr { get; set; } [JsonPropertyName("Message")] public string? Message { get; set; } [JsonPropertyName("Redirect")] public string? Redirect { get; set; } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/Responses/XboxSisuResponse.cs ================================================ using System.Text.Json.Serialization; namespace XboxAuthNet.XboxLive.Responses { public class XboxSisuResponse { [JsonPropertyName("DeviceToken")] public string? DeviceToken { get; set; } [JsonPropertyName("TitleToken")] public XboxAuthResponse? TitleToken { get; set; } [JsonPropertyName("UserToken")] public XboxAuthResponse? UserToken { get; set; } [JsonPropertyName("AuthorizationToken")] public XboxAuthResponse? AuthorizationToken { get; set; } [JsonPropertyName("WebPage")] public string? WebPage { get; set; } [JsonPropertyName("Sandbox")] public string? Sandbox { get; set; } [JsonPropertyName("UseModernGamerTag")] public string? UseModernGamertag { get; set; } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/XboxAuthClient.cs ================================================ using System.Net.Http; using XboxAuthNet.XboxLive.Requests; using XboxAuthNet.XboxLive.Responses; namespace XboxAuthNet.XboxLive; // https://github.com/PrismarineJS/prismarine-auth/blob/master/src/TokenManagers/XboxTokenManager.js public class XboxAuthClient { private readonly HttpClient _httpClient; public XboxAuthClient(HttpClient httpClient) => _httpClient = httpClient; public Task RequestUserToken(string rps) => RequestUserToken(new XboxUserTokenRequest() { AccessToken = rps }); public Task RequestUserToken(XboxUserTokenRequest request) => request.Send(_httpClient); public Task RequestXsts(string userToken) => RequestXsts(new XboxXstsRequest { UserToken = userToken }); public Task RequestXsts(string userToken, string relyingParty) => RequestXsts(new XboxXstsRequest { UserToken = userToken, RelyingParty = relyingParty }); public Task RequestXsts(XboxXstsRequest request) => request.Send(_httpClient); } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/XboxAuthConstants.cs ================================================ namespace XboxAuthNet.XboxLive { public class XboxAuthConstants { public const string XboxScope = "service::user.auth.xboxlive.com::MBI_SSL"; public const string XboxLiveRelyingParty = "http://xboxlive.com"; public const string XboxAuthRelyingParty = "http://auth.xboxlive.com"; public const string XboxEventsRelyingParty = "http://events.xboxlive.com"; public const string AzureTokenPrefix = "d="; public const string XboxTokenPrefix = "t="; } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/XboxAuthException.cs ================================================ using System; using System.Linq; using System.Net.Http.Headers; using System.Text.Json; using XboxAuthNet.XboxLive.Responses; namespace XboxAuthNet.XboxLive { public class XboxAuthException : Exception { public XboxAuthException(string message, int statusCode) : base(message) => StatusCode = statusCode; public XboxAuthException(string? error, string? message, string? redirect, int statusCode) : base(CreateMessageFromError(error, message, redirect)) => (Error, ErrorMessage, Redirect, StatusCode) = (error, message, redirect, statusCode); private static string CreateMessageFromError(params string?[] inputs) { return string.Join(", ", inputs.Where(x => !string.IsNullOrEmpty(x))); } public int StatusCode { get; private set; } public string? Error { get; private set; } // refer ErrorCodes.cs public string? ErrorMessage { get; private set; } public string? Redirect { get; private set; } public static XboxAuthException FromResponseBody(string responseBody, int statusCode) { try { var errRes = JsonSerializer.Deserialize(responseBody); if (string.IsNullOrEmpty(errRes?.XErr) && string.IsNullOrEmpty(errRes?.Message)) throw new FormatException(); return new XboxAuthException(errRes?.XErr, errRes?.Message, errRes?.Redirect, statusCode); } catch (JsonException) { throw new FormatException(); } } public static XboxAuthException FromResponseHeaders(HttpResponseHeaders headers, int statusCode) { string? xerr = null; if (headers.TryGetValues("X-Err", out var xerrValues)) xerr = xerrValues.FirstOrDefault(); string? message = null; if (headers.TryGetValues("WWW-Authenticate", out var authValues)) message = authValues.FirstOrDefault(); if (string.IsNullOrEmpty(xerr) && string.IsNullOrEmpty(message)) throw new FormatException(); return new XboxAuthException(ErrorHelper.TryConvertToHexErrorCode(xerr), message, null, statusCode); } } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/XboxAuthXuiClaimNames.cs ================================================ namespace XboxAuthNet.XboxLive { public static class XboxAuthXuiClaimNames { public const string Gamertag = "gtg"; public const string XboxUserId = "xid"; public const string UserHash = "uhs"; public const string AgeGroup = "agg"; public const string UserSettingsRestrictions = "usr"; public const string UserTitleRestrictions = "utr"; public const string Privileges = "prv"; } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/XboxDeviceTypes.cs ================================================ using System; using System.Collections.Generic; using System.Text; namespace XboxAuthNet.XboxLive { public class XboxDeviceTypes { public const string Win32 = "Win32"; public const string iOS = "iOS"; public const string Android = "Android"; public const string Nintendo = "Nintendo"; public const string WindowsOneCore = "WindowsOneCore"; } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/XboxGameTitles.cs ================================================ namespace XboxAuthNet.XboxLive { public static class XboxGameTitles { public const string MinecraftNintendoSwitch = "00000000441cc96b"; public const string MinecraftJava = "00000000402b5328"; public const string XboxAppIOS = "000000004c12ae6f"; public const string XboxGamepassIOS = "000000004c20a908"; public const string XboxAppPC = "000000004424da1f"; public const string Solitaire = "00000000440c1a11"; } } ================================================ FILE: XAU/Util/XboxAuthNet/XboxLive/XboxSignedClient.cs ================================================ using System.Net.Http; using XboxAuthNet.XboxLive.Crypto; using XboxAuthNet.XboxLive.Requests; using XboxAuthNet.XboxLive.Responses; namespace XboxAuthNet.XboxLive; public class XboxSignedClient { private readonly HttpClient _httpClient; private readonly IXboxRequestSigner _signer; public XboxSignedClient(HttpClient httpClient) { _httpClient = httpClient; _signer = new XboxRequestSigner(new ECDCertificatePopCryptoProvider()); } public XboxSignedClient(IXboxRequestSigner signer, HttpClient httpClient) { _signer = signer; _httpClient = httpClient; } public Task RequestSignedUserToken(string rps) => RequestSignedUserToken(new XboxSignedUserTokenRequest() { AccessToken = rps }); public Task RequestSignedUserToken(XboxSignedUserTokenRequest request) => request.Send(_httpClient, _signer); public Task RequestDeviceToken(string deivceType, string deviceVersion) => RequestDeviceToken(new XboxDeviceTokenRequest() { DeviceType = deivceType, DeviceVersion = deviceVersion }); public Task RequestDeviceToken(XboxDeviceTokenRequest request) => request.Send(_httpClient, _signer); public Task RequestTitleToken(string accessToken, string deviceToken) => RequestTitleToken(new XboxTitleTokenRequest() { AccessToken = accessToken, DeviceToken = deviceToken }); public Task RequestTitleToken(XboxTitleTokenRequest request) => request.Send(_httpClient, _signer); public Task SisuAuth(XboxSisuAuthRequest request) => request.Send(_httpClient, _signer); public Task RequestXstsToken(XboxXstsRequest request) => request.Send(_httpClient); public Task RequestSignedXstsToken(XboxSignedXstsRequest request) => request.Send(_httpClient, _signer); } ================================================ FILE: XAU/ViewModels/Pages/AchievementsViewModel.cs ================================================ using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Net.Http; using System.Text; using System.Windows.Data; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Wpf.Ui.Controls; using Wpf.Ui.Common; using Wpf.Ui.Contracts; using Wpf.Ui.Services; using XAU.Views.Pages; namespace XAU.ViewModels.Pages { public partial class AchievementsViewModel : ObservableObject, INavigationAware { [ObservableProperty] private bool _isInitialized = false; [ObservableProperty] private string _titleIDOverride = "0"; [ObservableProperty] private bool _unlockable = false; [ObservableProperty] private bool _titleIDEnabled = false; [ObservableProperty] private ObservableCollection _achievements = new ObservableCollection(); [ObservableProperty] private ObservableCollection _dGAchievements = new ObservableCollection(); [ObservableProperty] public string _gameInfo = ""; [ObservableProperty] private string _gameName = ""; [ObservableProperty] private bool _isUnlockAllEnabled = false; [ObservableProperty] private string _searchText = ""; public static string TitleID = "0"; private bool IsTitleIDValid = false; public static bool NewGame = false; public static bool IsSelectedGame360; private AchievementsResponse AchievementResponse = new AchievementsResponse(); private Xbox360AchievementResponse Xbox360AchievementResponse = new Xbox360AchievementResponse(); private Dictionary _unlockedAchievements = new Dictionary(); private GameTitle GameInfoResponse = new GameTitle(); // TODO: this needs to be updated if language changes private Lazy _xboxRestAPI = new Lazy(() => new XboxRestAPI(HomeViewModel.XAUTH)); public static bool SpoofingUpdate = false; private bool IsFiltered = false; private bool IsEventBased = false; private dynamic EventsData = (dynamic)(new JObject()); public static string EventsToken; public AchievementsViewModel(ISnackbarService snackbarService, IContentDialogService contentDialogService, INavigationService navigationService) { _snackbarService = snackbarService; _contentDialogService = contentDialogService; _navigationService = navigationService; } private readonly IContentDialogService _contentDialogService; private readonly ISnackbarService _snackbarService; private readonly INavigationService _navigationService; private TimeSpan _snackbarDuration = TimeSpan.FromSeconds(2); public class DGAchievement { public int Index { get; set; } public int ID { get; set; } public string? Name { get; set; } public string? Description { get; set; } public bool IsSecret { get; set; } public DateTime DateUnlocked { get; set; } public int Gamerscore { get; set; } public float RarityPercentage { get; set; } public string? RarityCategory { get; set; } public string? ProgressState { get; set; } public bool IsUnlockable { get; set; } } public async void OnNavigatedTo() { if (HomeViewModel.Settings.AutoSpooferEnabled) { if (!GameInfoResponse.Titles.Any() && !String.IsNullOrWhiteSpace(GameInfoResponse.Xuid)) { _snackbarService.Show("Error: Game Info Response Contained No Titles", $"There were no titles returned from the API", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); } else { if (HomeViewModel.SpoofingStatus == 1 && !!string.IsNullOrWhiteSpace(GameInfo)) { if (HomeViewModel.SpoofedTitleID == TitleIDOverride) { GameInfo = "Manually Spoofing"; GameName = GameInfoResponse.Titles[0].Name; } else { GameInfo = "Spoofing Another Game"; GameName = GameInfoResponse.Titles[0].Name; } } else if (HomeViewModel.SpoofingStatus == 0 && !string.IsNullOrWhiteSpace(GameInfo)) { SpoofGame(); } } } if (IsInitialized && NewGame) await RefreshAchievements(); if (TitleID != "0") { TitleIDOverride = TitleID; TitleID = "0"; } if (HomeViewModel.InitComplete && TitleIDOverride == "0") TitleIDEnabled = true; if (!IsInitialized && HomeViewModel.InitComplete && TitleIDOverride != "0") InitializeViewModel(); } public void OnNavigatedFrom() { } private async void InitializeViewModel() { if (IsSelectedGame360) Unlockable = false; await LoadGameInfo(); await LoadAchievements(); if (HomeViewModel.Settings.AutoSpooferEnabled) SpoofGame(); TitleIDEnabled = true; IsInitialized = true; NewGame = false; } private async Task LoadGameInfo() { // Check for a valid TitleID and set overrides if (TitleID != "0") { TitleIDOverride = TitleID; TitleID = "0"; } GameInfo = string.Empty; // Fetch game information var gameInfoResponse = await _xboxRestAPI.Value.GetGameTitleAsync(HomeViewModel.XUIDOnly, TitleIDOverride); // Handle response validation and set properties accordingly if (gameInfoResponse?.Titles?.Any() != true) { GameName = "Error"; IsTitleIDValid = false; return; } var gameTitle = gameInfoResponse.Titles.FirstOrDefault(); if (gameTitle != null) { IsSelectedGame360 = gameTitle.Devices.Contains("Xbox360") || gameTitle.Devices.Contains("Mobile"); GameName = gameTitle.Name; IsTitleIDValid = true; } } private async void SpoofGame() { if (HomeViewModel.SpoofingStatus == 1) { if (HomeViewModel.SpoofedTitleID == TitleIDOverride) { GameInfo = "Manually Spoofing"; GameName = GameInfoResponse.Titles[0].Name; } else { GameInfo = "Spoofing Another Game"; GameName = GameInfoResponse.Titles[0].Name; } } else { HomeViewModel.AutoSpoofedTitleID = TitleIDOverride; HomeViewModel.SpoofingStatus = 2; GameInfo = "Auto Spoofing"; if (GameInfoResponse.Titles.Any()) { GameName = GameInfoResponse.Titles[0].Name; } await Task.Run(() => Spoofing()); if (HomeViewModel.SpoofingStatus == 1) { if (HomeViewModel.SpoofedTitleID == HomeViewModel.AutoSpoofedTitleID) { GameInfo = "Manually Spoofing"; GameName = GameInfoResponse.Titles[0].Name; } else { GameInfo = "Spoofing Another Game"; GameName = GameInfoResponse.Titles[0].Name; } } HomeViewModel.AutoSpoofedTitleID = "0"; } } public async Task Spoofing() { await _xboxRestAPI.Value.SendHeartbeatAsync(HomeViewModel.XUIDOnly, HomeViewModel.AutoSpoofedTitleID); var i = 0; Thread.Sleep(1000); SpoofingUpdate = false; while (!SpoofingUpdate) { if (i == 300) { await _xboxRestAPI.Value.SendHeartbeatAsync(HomeViewModel.XUIDOnly, HomeViewModel.AutoSpoofedTitleID); i = 0; } else { if (SpoofingUpdate) { break; } i++; } Thread.Sleep(1000); } } private async Task LoadAchievements() { Achievements.Clear(); DGAchievements.Clear(); // clears unlocked achievements from dictionary _unlockedAchievements.Clear(); if (!IsTitleIDValid) return; if (!IsSelectedGame360) { Unlockable = true; AchievementResponse = await _xboxRestAPI.Value.GetAchievementsForTitleAsync(HomeViewModel.XUIDOnly, TitleIDOverride); try { if (AchievementResponse.achievements[0].progression.requirements.Any()) { if (AchievementResponse.achievements[0].progression.requirements[0].id != StringConstants.ZeroUid) { Unlockable = false; } else { Unlockable = true; } } } catch { _snackbarService.Show("Error: No Achievements", $"There were no achievements returned from the API", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } for (int i = 0; i < AchievementResponse.achievements.Count; i++) { //absolutely fucking dogwater event based check if (AchievementResponse.achievements[i].progression.requirements.Any()) { if (AchievementResponse.achievements[i].progression.requirements[0].id != StringConstants.ZeroUid) { Unlockable = false; IsEventBased = true; } else { Unlockable = true; IsEventBased = false; } } var rewardnameplaceholder = ""; var rewarddescriptionplaceholder = ""; var rewardvalueplaceholder = ""; var rewardtypeplaceholder = ""; var rewardmediaAssetplaceholder = ""; var rewardvalueTypeplaceholder = ""; try { rewardnameplaceholder = AchievementResponse.achievements[i].rewards[0].name; rewarddescriptionplaceholder = AchievementResponse.achievements[i].rewards[0].description; rewardvalueplaceholder = AchievementResponse.achievements[i].rewards[0].value; rewardtypeplaceholder = AchievementResponse.achievements[i].rewards[0].type; //rewardmediaAssetplaceholder = AchievementResponse.achievements[i].rewards[0].mediaAsset; rewardvalueTypeplaceholder = AchievementResponse.achievements[i].rewards[0].valueType; } catch { rewardnameplaceholder = "N/A"; rewarddescriptionplaceholder = "N/A"; rewardvalueplaceholder = "N/A"; rewardtypeplaceholder = "N/A"; rewardmediaAssetplaceholder = "N/A"; rewardvalueTypeplaceholder = "N/A"; } var mediaAsset = new MediaAsset { name = AchievementResponse.achievements[i].mediaAssets[0].name, type = AchievementResponse.achievements[i].mediaAssets[0].type, url = AchievementResponse.achievements[i].mediaAssets[0].url }; var titleAssociation = new TitleAssociation { name = AchievementResponse.achievements[i].titleAssociations[0].name, id = AchievementResponse.achievements[i].titleAssociations[0].id }; var progression = new AchievementProgression { timeUnlocked = AchievementResponse.achievements[i].progression.timeUnlocked }; var rewards = new AchievementRewards { name = rewardnameplaceholder, description = rewarddescriptionplaceholder, value = rewardvalueplaceholder, type = rewardtypeplaceholder, mediaAsset = mediaAsset, valueType = rewardvalueTypeplaceholder }; Achievements.Add(new OneCoreAchievementResponse() { id = AchievementResponse.achievements[i].id, serviceConfigId = AchievementResponse.achievements[i].serviceConfigId, name = AchievementResponse.achievements[i].name, titleAssociations = new List() { titleAssociation }, progressState = AchievementResponse.achievements[i].progressState, progression = progression, mediaAssets = new List() { mediaAsset }, platforms = AchievementResponse.achievements[i].platforms, isSecret = AchievementResponse.achievements[i].isSecret, description = AchievementResponse.achievements[i].description, lockedDescription = AchievementResponse.achievements[i].lockedDescription, productId = AchievementResponse.achievements[i].productId, achievementType = AchievementResponse.achievements[i].achievementType, participationType = AchievementResponse.achievements[i].participationType, timeWindow = AchievementResponse.achievements[i].timeWindow, rewards = new List() { rewards }, estimatedTime = AchievementResponse.achievements[i].estimatedTime, deeplink = AchievementResponse.achievements[i].deeplink, isRevoked = AchievementResponse.achievements[i].isRevoked, raritycurrentCategory = AchievementResponse.achievements[i].rarity.currentCategory, raritycurrentPercentage = AchievementResponse.achievements[i].rarity.currentPercentage } ); } foreach (var achievement in Achievements) { var gamerscore = 0; if (achievement.rewards[0].type == StringConstants.Gamerscore) { gamerscore = int.Parse(achievement.rewards[0].value); } DGAchievements.Add(new DGAchievement() { Index = Achievements.IndexOf(achievement), ID = int.Parse(achievement.id), Name = achievement.name, Description = achievement.description, IsSecret = achievement.isSecret, DateUnlocked = DateTime.Parse(achievement.progression.timeUnlocked), Gamerscore = gamerscore, RarityPercentage = float.Parse(achievement.raritycurrentPercentage, CultureInfo.InvariantCulture), RarityCategory = achievement.raritycurrentCategory, ProgressState = achievement.progressState, IsUnlockable = achievement.progressState != StringConstants.Achieved && Unlockable && !IsEventBased }); } } else { Unlockable = false; Xbox360AchievementResponse = await _xboxRestAPI.Value.GetAchievementsFor360TitleAsync(HomeViewModel.XUIDOnly, TitleIDOverride); if (Xbox360AchievementResponse?.achievements.Count == 0) { IsSelectedGame360 = false; LoadAchievements(); return; } //cut down version of the code to display minimal information about 360 achievements for (int i = 0; i < Xbox360AchievementResponse?.achievements.Count; i++) { var rewards = new AchievementRewards { value = Xbox360AchievementResponse.achievements[i].gamerscore.ToString(), valueType = "N/a" }; var progression = new AchievementProgression { timeUnlocked = Xbox360AchievementResponse.achievements[i].timeUnlocked }; Achievements.Add(new OneCoreAchievementResponse() { id = Xbox360AchievementResponse.achievements[i].id.ToString(), name = Xbox360AchievementResponse.achievements[i].name, isSecret = Xbox360AchievementResponse.achievements[i].isSecret, description = Xbox360AchievementResponse.achievements[i].description, rewards = new List() { rewards }, raritycurrentCategory = Xbox360AchievementResponse.achievements[i].rarity.currentCategory, raritycurrentPercentage = Xbox360AchievementResponse.achievements[i].rarity.currentPercentage, progression = progression } ); } foreach (var achievement in Achievements) { var gamerscore = 0; if (achievement.rewards[0].type == "Gamerscore") { gamerscore = int.Parse(achievement.rewards[0].value); } DGAchievements.Add(new DGAchievement() { Index = Achievements.IndexOf(achievement), ID = int.Parse(achievement.id), Name = achievement.name, Description = achievement.description, IsSecret = achievement.isSecret, DateUnlocked = DateTime.Parse(achievement.progression.timeUnlocked), Gamerscore = gamerscore, RarityPercentage = float.Parse(achievement.raritycurrentPercentage, CultureInfo.InvariantCulture), RarityCategory = achievement.raritycurrentCategory, ProgressState = achievement.progressState, IsUnlockable = achievement.progressState != StringConstants.Achieved && Unlockable }); } } if (IsSelectedGame360) { _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, new SymbolIcon(SymbolRegular.Warning24), _snackbarDuration); IsUnlockAllEnabled = false; return; } if (IsEventBased) { //Event based logic string DataPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\XAU\\Events\\Data.json"; var data = JObject.Parse(File.ReadAllText(DataPath)); JArray SupportedGamesJ = (JArray)data["SupportedTitleIDs"]; List SupportedGames = SupportedGamesJ.ToObject>(); if (SupportedGames.Contains(int.Parse(TitleIDOverride))) { Unlockable = true; EventsData = (dynamic)(JObject)data[TitleIDOverride]; foreach (var achievement in DGAchievements) { if (EventsData.Achievements.ContainsKey(achievement.ID.ToString()) && achievement.ProgressState != StringConstants.Achieved) { achievement.IsUnlockable = true; } } } CollectionViewSource.GetDefaultView(DGAchievements).Refresh(); } if (!Unlockable) { _snackbarService.Show("Warning: Unsupported Game", $"This tool does not support this Event Based title", ControlAppearance.Caution, new SymbolIcon(SymbolRegular.Warning24), _snackbarDuration); } else if (IsEventBased && EventsData.FullySupported == false) { _snackbarService.Show("Warning: Partially Unsupported Game", $"This tool does not fully support this title. Not all achievements are unlockable", ControlAppearance.Caution, new SymbolIcon(SymbolRegular.Warning24), _snackbarDuration); } if (HomeViewModel.Settings.UnlockAllEnabled && Unlockable && !IsEventBased) IsUnlockAllEnabled = Unlockable; else IsUnlockAllEnabled = false; } public async void UnlockAchievement(int AchievementIndex) { if (!IsEventBased) { try { await _xboxRestAPI.Value.UnlockTitleBasedAchievementAsync(AchievementResponse.achievements[0].serviceConfigId, AchievementResponse.achievements[0].titleAssociations[0].id, HomeViewModel.XUIDOnly, DGAchievements[AchievementIndex].ID.ToString(), HomeViewModel.Settings.FakeSignatureEnabled); _snackbarService.Show("Achievement Unlocked", $"{DGAchievements[AchievementIndex].Name} has been unlocked", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration); DGAchievements[AchievementIndex].IsUnlockable = false; DGAchievements[AchievementIndex].ProgressState = StringConstants.Achieved; DGAchievements[AchievementIndex].DateUnlocked = DateTime.Now; // Add achievement to the dictionary. this will fix search & filter unlockable state var unlockedAchievement = DGAchievements[AchievementIndex]; unlockedAchievement.IsUnlockable = false; unlockedAchievement.ProgressState = StringConstants.Achieved; unlockedAchievement.DateUnlocked = DateTime.Now; if (!_unlockedAchievements.ContainsKey(unlockedAchievement.ID)) { _unlockedAchievements.Add(unlockedAchievement.ID, unlockedAchievement); } CollectionViewSource.GetDefaultView(DGAchievements).Refresh(); } catch (HttpRequestException ex) { _snackbarService.Show("Error: Achievement Not Unlocked", $"{DGAchievements[AchievementIndex].Name} was not unlocked", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); } } else { if (EventsToken == null || HomeViewModel.IsEventsTokenExpired()) { ContentDialogResult result = await _contentDialogService.ShowSimpleDialogAsync( new SimpleContentDialogCreateOptions() { Title = EventsToken == null ? "Error: You have not set an events token" : "Error: Your events token has expired", Content = EventsToken == null ? "To unlock event based games you must supply an events token. You can set one up in Settings." : "Your events token has expired and needs to be refreshed before unlocking.", PrimaryButtonText = "Go to Settings", CloseButtonText = "Close", }); if (result == ContentDialogResult.Primary) _navigationService.Navigate(typeof(SettingsPage)); return; } // TODO: move this over to the rest api? var requestbody = File.ReadAllText(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + $"\\XAU\\Events\\{TitleIDOverride}.json"); DateTime timestamp = DateTime.UtcNow; foreach (var i in EventsData.Achievements[DGAchievements[AchievementIndex].ID.ToString()]) { var ReplacementData = i.Value; switch (ReplacementData.ReplacementType.ToString()) { case "Replace": { requestbody = requestbody.Replace(ReplacementData.Target.ToString(), ReplacementData.Replacement.ToString()); break; } case "RangeInt": { int min = ReplacementData.Min; int max = ReplacementData.Max; Random random = new Random(); int randomint = random.Next(min, max); requestbody = requestbody.Replace(ReplacementData.Target.ToString(), randomint.ToString()); break; } case "RangeFloat": { float min = ReplacementData.Min; float max = ReplacementData.Max; Random random = new Random(); float randomfloat = (float)random.NextDouble() * (max - min) + min; requestbody = requestbody.Replace(ReplacementData.Target.ToString(), randomfloat.ToString()); break; } case "StupidFuckingLDAPTimestamp": { long ldapTimestamp = DateTime.Now.ToFileTime(); requestbody = requestbody.Replace(ReplacementData.Target.ToString(), ldapTimestamp.ToString()); break; } default: { _snackbarService.Show("Error: Bad Achievement Data", "Something went wrong with the achievement data", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } } } requestbody = requestbody.Replace("REPLACETIME", timestamp.ToString("yyyy-MM-ddTHH:mm:ss.fffffffZ")); requestbody = requestbody.Replace("REPLACESEQ", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()); requestbody = requestbody.Replace("REPLACEXUID", HomeViewModel.XUIDOnly); requestbody = JObject.Parse(requestbody).ToString(Formatting.None); var bodyconverted = new StringContent(requestbody, Encoding.UTF8, "application/x-json-stream"); try { await _xboxRestAPI.Value.UnlockEventBasedAchievement(EventsToken, bodyconverted); _snackbarService.Show("Achievement Unlocked", $"{DGAchievements[AchievementIndex].Name} has been unlocked", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration); DGAchievements[AchievementIndex].IsUnlockable = false; DGAchievements[AchievementIndex].ProgressState = "Achieved"; DGAchievements[AchievementIndex].DateUnlocked = DateTime.Now; CollectionViewSource.GetDefaultView(DGAchievements).Refresh(); } catch { _snackbarService.Show("Error: Achievement Not Unlocked", $"{DGAchievements[AchievementIndex].Name} was not unlocked", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); } } } [RelayCommand] public async Task UnlockAll() { var lockedAchievementIds = Achievements.Where(o => o.progressState != StringConstants.Achieved).Select(o => o.id).ToList(); try { await _xboxRestAPI.Value.UnlockTitleBasedAchievementsAsync(serviceConfigId: AchievementResponse.achievements[0].serviceConfigId, titleId: AchievementResponse.achievements[0].titleAssociations[0].id, xuid: HomeViewModel.XUIDOnly, achievementIds: lockedAchievementIds, useFakeSignature: HomeViewModel.Settings.FakeSignatureEnabled); _snackbarService.Show("All Achievements Unlocked", $"All Achievements for this game have been unlocked", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration); var unlocktime = DateTime.Now; foreach (DGAchievement achievement in DGAchievements) { if (achievement.ProgressState != StringConstants.Achieved) { achievement.IsUnlockable = false; achievement.ProgressState = StringConstants.Achieved; achievement.DateUnlocked = unlocktime; } } CollectionViewSource.GetDefaultView(DGAchievements).Refresh(); } catch (HttpRequestException hre) { _snackbarService.Show("Error: Achievements Not Unlocked", $"{hre.Message}", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); } } [RelayCommand] public async Task RefreshAchievements() { // clears unlocked achievements from dictionary _unlockedAchievements.Clear(); await LoadGameInfo(); await LoadAchievements(); NewGame = false; if (HomeViewModel.Settings.AutoSpooferEnabled) SpoofGame(); } [RelayCommand] public async Task SearchAndFilterAchievements() { try { if (IsEventBased) { string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU", "Events", "Data.json"); var data = JObject.Parse(File.ReadAllText(DataPath)); JArray SupportedGamesJ = (JArray)data["SupportedTitleIDs"]; List SupportedGames = SupportedGamesJ.ToObject>(); if (SupportedGames.Contains(int.Parse(TitleIDOverride))) { Unlockable = true; EventsData = (dynamic)data[TitleIDOverride]; } } CollectionViewSource.GetDefaultView(DGAchievements).Refresh(); if (string.IsNullOrWhiteSpace(SearchText) && !IsFiltered) { _snackbarService.Show("Error", $"Please Enter Query Text", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } DGAchievements.Clear(); if (string.IsNullOrWhiteSpace(SearchText) && IsFiltered) { foreach (var achievement in Achievements) { var gamerscore = 0; if (achievement.rewards[0].type == StringConstants.Gamerscore) { gamerscore = int.Parse(achievement.rewards[0].value); } var dgAchievement = new DGAchievement() { Index = DGAchievements.Count, ID = int.Parse(achievement.id), Name = achievement.name, Description = achievement.description, IsSecret = achievement.isSecret, DateUnlocked = DateTime.Parse(achievement.progression.timeUnlocked), Gamerscore = gamerscore, RarityPercentage = float.Parse(achievement.raritycurrentPercentage, CultureInfo.InvariantCulture), RarityCategory = achievement.raritycurrentCategory, ProgressState = achievement.progressState, IsUnlockable = achievement.progressState != StringConstants.Achieved && Unlockable && !IsEventBased }; // Override with the state from _unlockedAchievements dictionary if it exists. if (_unlockedAchievements.ContainsKey(dgAchievement.ID)) { var unlocked = _unlockedAchievements[dgAchievement.ID]; dgAchievement.IsUnlockable = unlocked.IsUnlockable; dgAchievement.ProgressState = unlocked.ProgressState; dgAchievement.DateUnlocked = unlocked.DateUnlocked; } DGAchievements.Add(dgAchievement); } if (IsEventBased && Unlockable) { foreach (var achievement in DGAchievements) { if (EventsData.Achievements.ContainsKey(achievement.ID.ToString()) && achievement.ProgressState != StringConstants.Achieved) { achievement.IsUnlockable = true; } } CollectionViewSource.GetDefaultView(DGAchievements).Refresh(); } IsFiltered = false; return; } bool achievementsFound = false; foreach (var achievement in Achievements) { if (achievement.name.Contains(SearchText, StringComparison.OrdinalIgnoreCase) || achievement.description.Contains(SearchText, StringComparison.OrdinalIgnoreCase)) { var gamerscore = 0; if (achievement.rewards[0].type == StringConstants.Gamerscore) { gamerscore = int.Parse(achievement.rewards[0].value); } var dgAchievement = new DGAchievement() { Index = DGAchievements.Count, ID = int.Parse(achievement.id), Name = achievement.name, Description = achievement.description, IsSecret = achievement.isSecret, DateUnlocked = DateTime.Parse(achievement.progression.timeUnlocked), Gamerscore = gamerscore, RarityPercentage = float.Parse(achievement.raritycurrentPercentage, CultureInfo.InvariantCulture), RarityCategory = achievement.raritycurrentCategory, ProgressState = achievement.progressState, IsUnlockable = achievement.progressState != StringConstants.Achieved && Unlockable && !IsEventBased }; // Override with the state from _unlockedAchievements dictionary if it exists. if (_unlockedAchievements.ContainsKey(dgAchievement.ID)) { var unlockedAchievement = _unlockedAchievements[dgAchievement.ID]; dgAchievement.IsUnlockable = unlockedAchievement.IsUnlockable; dgAchievement.ProgressState = unlockedAchievement.ProgressState; dgAchievement.DateUnlocked = unlockedAchievement.DateUnlocked; } DGAchievements.Add(dgAchievement); achievementsFound = true; } } if (!achievementsFound) { _snackbarService.Show("Error", $"No Achievements Found", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); } if (IsEventBased && Unlockable) { foreach (var achievement in DGAchievements) { if (EventsData.Achievements.ContainsKey(achievement.ID.ToString()) && achievement.ProgressState != StringConstants.Achieved) { achievement.IsUnlockable = true; } } CollectionViewSource.GetDefaultView(DGAchievements).Refresh(); } IsFiltered = true; } catch (Exception ex) { // Log exception (ex) if necessary _snackbarService.Show("Error", "An error occurred while searching. Please try again.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); } await Task.CompletedTask; } } } ================================================ FILE: XAU/ViewModels/Pages/DebugViewModel.cs ================================================ using Newtonsoft.Json.Linq; using System.IO; using System.Net.Http; using System.Text; using Wpf.Ui.Controls; using Newtonsoft.Json; using Wpf.Ui.Contracts; namespace XAU.ViewModels.Pages { public partial class DebugViewModel : ObservableObject, INavigationAware { private bool _isInitialized = false; public void OnNavigatedTo() { if (!_isInitialized) InitializeViewModel(); } public void OnNavigatedFrom() { } private void InitializeViewModel() { _isInitialized = true; } public DebugViewModel(ISnackbarService snackbarService, IContentDialogService contentDialogService) { _snackbarService = snackbarService; _contentDialogService = contentDialogService; } private readonly ISnackbarService _snackbarService; private TimeSpan _snackbarDuration = TimeSpan.FromSeconds(2); private readonly IContentDialogService _contentDialogService; [RelayCommand] public void TestEventReplacements() { int failedAchievements = 0; int successfulAchievements = 0; List errors = new List(); string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU", "Events", "Data.json"); var data = JObject.Parse(File.ReadAllText(DataPath)); JArray SupportedGamesJ = (JArray)data["SupportedTitleIDs"]; string Achievement = ""; DateTime timestamp = DateTime.UtcNow; foreach (var game in SupportedGamesJ) { var requestbody = File.ReadAllText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU", "Events", $"{game}.json")); var EventsData = (dynamic)(JObject)data[game.ToString()]; foreach (var i in EventsData.Achievements) { try { foreach (var j in i) { Achievement = i.Name.ToString(); foreach (var k in j) { var ReplacementData = k.Value; switch (ReplacementData.ReplacementType.ToString()) { case "Replace": { requestbody = requestbody.Replace(ReplacementData.Target.ToString(), ReplacementData.Replacement.ToString()); break; } case "RangeInt": { int min = ReplacementData.Min; int max = ReplacementData.Max; Random random = new Random(); int randomint = random.Next(min, max); requestbody = requestbody.Replace(ReplacementData.Target.ToString(), randomint.ToString()); break; } case "RangeFloat": { float min = ReplacementData.Min; float max = ReplacementData.Max; Random random = new Random(); float randomfloat = (float)random.NextDouble() * (max - min) + min; requestbody = requestbody.Replace(ReplacementData.Target.ToString(), randomfloat.ToString()); break; } case "StupidFuckingLDAPTimestamp": { long ldapTimestamp = DateTime.Now.ToFileTime(); requestbody = requestbody.Replace(ReplacementData.Target.ToString(), ldapTimestamp.ToString()); break; } default: { //_snackbarService.Show("Error: Bad Achievement Data", "Something went wrong with the achievement data", ControlAppearance.Danger, // new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } } } } requestbody = requestbody.Replace("REPLACETIME", timestamp.ToString("yyyy-MM-ddTHH:mm:ss.fffffffZ")); requestbody = requestbody.Replace("REPLACESEQ", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()); requestbody = requestbody.Replace("REPLACEXUID", HomeViewModel.XUIDOnly); requestbody = JObject.Parse(requestbody).ToString(Formatting.None); var bodyconverted = new StringContent(requestbody, Encoding.UTF8, "application/x-json-stream"); successfulAchievements++; } catch (Exception ex) { failedAchievements++; errors.Add($"Game: {game}, Achievement:{Achievement}, Error: {ex.Message}"); } } } // Write errors to a file File.WriteAllLines(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU", "Events", "Errors.log"), errors); // Show message box with the summary _contentDialogService.ShowSimpleDialogAsync( new SimpleContentDialogCreateOptions() { Title = "Test Results", Content = $"Successful Achievements: {successfulAchievements}\nUnsuccessful Achievements: {failedAchievements}", CloseButtonText = "Close" }); } } } ================================================ FILE: XAU/ViewModels/Pages/GamesViewModel.cs ================================================ using System.Collections.ObjectModel; using System.ComponentModel; using Wpf.Ui.Common; using Wpf.Ui.Contracts; using Wpf.Ui.Controls; using XAU.Views.Pages; namespace XAU.ViewModels.Pages { public partial class GamesViewModel(ISnackbarService snackbarService, INavigationService navigationService) : ObservableObject, INavigationAware, INotifyPropertyChanged { [ObservableProperty] private string _xuidOverride = "0"; [ObservableProperty] private ObservableCollection _games = new ObservableCollection(); [ObservableProperty] private ObservableCollection _gamesPaged = new ObservableCollection(); [ObservableProperty] private string _searchLabel = "Search 0 Games"; [ObservableProperty] private GridLength _gamesListHeight = new GridLength(0, GridUnitType.Star); [ObservableProperty] private GridLength _loadingHeight = new GridLength(1, GridUnitType.Star); [ObservableProperty] private double _loadingSize = 200; [ObservableProperty] private string _searchText = ""; [ObservableProperty] private List _filterOptions = new List() { "All", "Xbox One/Series", "PC", "Xbox 360", "Win32", "Incomplete Games" }; [ObservableProperty] private int _filterIndex = 0; [ObservableProperty] private int _numPages = 0; [ObservableProperty] private ObservableCollection _pageOptions = new ObservableCollection(); [ObservableProperty] private int _currentPage = 0; [ObservableProperty] private bool _isInitialized = false; TitlesList GamesResponse = new TitlesList(); public bool PageReset = true; public class Game { public required string Title { get; set; } public required string Image { get; set; } public required string Gamerscore { get; set; } public required string CurrentAchievements { get; set; } public required string Progress { get; set; } public required string Index { get; set; } } // TODO: this needs to be updated if language changes private Lazy _xboxRestAPI = new Lazy(() => new XboxRestAPI(HomeViewModel.XAUTH)); private readonly IContentDialogService _contentDialogService; private readonly ISnackbarService _snackbarService = snackbarService; private TimeSpan _snackbarDuration = TimeSpan.FromSeconds(2); public async void OnNavigatedTo() { if (!IsInitialized && HomeViewModel.InitComplete) await InitializeViewModel(); } public void OnNavigatedFrom() { } private async Task InitializeViewModel() { XuidOverride = HomeViewModel.XUIDOnly; IsInitialized = true; await GetGamesList(); } [RelayCommand] private async Task GetGamesList() { if (string.IsNullOrWhiteSpace(XuidOverride) || string.IsNullOrEmpty(XuidOverride)) { _snackbarService.Show( "Error", "XUID Override cannot be empty.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration ); return; } Games.Clear(); GamesPaged.Clear(); LoadingStart(); GamesResponse = await _xboxRestAPI.Value.GetGamesListAsync(XuidOverride) ?? new TitlesList(); LoadGame(); } private void LoadGame() { if (SearchText.Length > 0) { SearchAndFilterGames(); } else { FilterGames(); } } public async Task OpenAchievements(string index) { AchievementsViewModel.TitleID = GamesResponse.Titles[int.Parse(index)].TitleId; AchievementsViewModel.IsSelectedGame360 = GamesResponse.Titles[int.Parse(index)].Devices.Contains("Xbox360") || GamesResponse.Titles[int.Parse(index)].Devices.Contains("Mobile"); AchievementsViewModel.NewGame = true; navigationService.Navigate(typeof(AchievementsPage)); await Task.CompletedTask; } [RelayCommand] public void SearchAndFilterGames() { Games.Clear(); GamesPaged.Clear(); LoadingStart(); if (FilterIndex != 0) { switch (FilterIndex) { case 1: for (int i = 0; i < GamesResponse.Titles.Count; i++) { if (GamesResponse.Titles[i].Devices.Contains("XboxSeries") || GamesResponse.Titles[i].Devices.Contains("XboxOne")) { if (!GamesResponse.Titles[i].Name.ToLower().Contains(SearchText.ToLower())) continue; AddGame(i); } } break; case 2: for (int i = 0; i < GamesResponse.Titles.Count; i++) { if (GamesResponse.Titles[i].Devices.Contains("PC")) { if (!GamesResponse.Titles[i].Name.ToLower().Contains(SearchText.ToLower())) continue; AddGame(i); } } break; case 3: for (int i = 0; i < GamesResponse.Titles.Count; i++) { if (GamesResponse.Titles[i].Devices.Contains("Xbox360")) { if (!GamesResponse.Titles[i].Name.ToLower().Contains(SearchText.ToLower())) continue; AddGame(i); } } break; case 4: for (int i = 0; i < GamesResponse.Titles.Count; i++) { if (GamesResponse.Titles[i].Devices.Contains("Win32")) { if (!GamesResponse.Titles[i].Name.ToLower().Contains(SearchText.ToLower())) continue; AddGame(i); }; } break; case 5: for (int i = 0; i < GamesResponse.Titles.Count; i++) { if (double.TryParse(GamesResponse.Titles[i].Achievement.ProgressPercentage.ToString(), out double progress) && progress < 100) { if (!GamesResponse.Titles[i].Name.ToLower().Contains(SearchText.ToLower())) continue; AddGame(i); } } break; } } else { for (int i = 0; i < GamesResponse.Titles.Count; i++) { var title = GamesResponse.Titles[i]; if (!title.Name.ToLower().Contains(SearchText.ToLower())) continue; AddGame(i); } } LoadingEnd(); SearchLabel = $"Search {GamesResponse.Titles.Count.ToString()} Games"; if (Games.Count() == 0) { _snackbarService.Show("Error", $"No Games Found", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); NumPages = 0; return; } NumPages = (int)Math.Ceiling(Games.Count / 252.0); PageReset = true; PageOptions.Clear(); for (int i = 1; i <= NumPages; i++) { PageOptions.Add(i.ToString()); } PageReset = true; CurrentPage = 0; GamesPaged.Clear(); for (int i = ((252 * CurrentPage)); i < (252 * (CurrentPage + 1)); i++) { if (Games.Count > i) { GamesPaged.Add(Games[i]); } } } [RelayCommand] public void FilterGames() { if (!IsInitialized) { return; } if (SearchText.Length > 0) { SearchAndFilterGames(); return; } GamesPaged.Clear(); LoadingStart(); Games.Clear(); if (FilterIndex != 0) { switch (FilterIndex) { case 1: for (int i = 0; i < GamesResponse.Titles.Count; i++) { if (GamesResponse.Titles[i].Devices.Contains("XboxSeries") || GamesResponse.Titles[i].Devices.Contains("XboxOne")) AddGame(i); } break; case 2: for (int i = 0; i < GamesResponse.Titles.Count; i++) { if (GamesResponse.Titles[i].Devices.Contains("PC")) AddGame(i); } break; case 3: for (int i = 0; i < GamesResponse.Titles.Count; i++) { if (GamesResponse.Titles[i].Devices.Contains("Xbox360")) AddGame(i); } break; case 4: for (int i = 0; i < GamesResponse.Titles.Count; i++) { if (GamesResponse.Titles[i].Devices.Contains("Win32")) AddGame(i); } break; case 5: for (int i = 0; i < GamesResponse.Titles.Count; i++) { if (double.TryParse(GamesResponse.Titles[i].Achievement.ProgressPercentage.ToString(), out double progress) && progress < 100) { if (!GamesResponse.Titles[i].Name.ToLower().Contains(SearchText.ToLower())) continue; AddGame(i); } } break; } } else { for (int i = 0; i < GamesResponse.Titles.Count; i++) { AddGame(i); } } LoadingEnd(); SearchLabel = $"Search {GamesResponse.Titles.Count.ToString()} Games"; if (Games.Count() == 0) { _snackbarService.Show("Error", $"No Games Found", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); NumPages = 0; return; } NumPages = (int)Math.Ceiling(Games.Count / 252.0); PageReset = true; PageOptions.Clear(); for (int i = 1; i <= NumPages; i++) { PageOptions.Add(i.ToString()); } PageReset = true; CurrentPage = 0; GamesPaged.Clear(); for (int i = ((252 * CurrentPage)); i < (252 * (CurrentPage + 1)); i++) { if (Games.Count > i) { GamesPaged.Add(Games[i]); } } } private void AddGame(int index) { var title = GamesResponse.Titles[index]; var EditedImage = !string.IsNullOrEmpty(title.DisplayImage?.ToString()) ? title.DisplayImage.ToString() : "pack://application:,,,/Assets/cirno.png"; if (EditedImage.Contains("store-images.s-microsoft.com")) { EditedImage = EditedImage + "?w=256&h=256&format=jpg"; } Games.Add(new Game() { Title = title.Name.ToString(), CurrentAchievements = title.Achievement.CurrentAchievements.ToString(), Gamerscore = title.Achievement.CurrentGamerscore.ToString() + "/" + title.Achievement.TotalGamerscore.ToString(), Progress = title.Achievement.ProgressPercentage.ToString(), Image = EditedImage, //"pack://application:,,,/Assets/cirno.png", // Index = index.ToString() }); } [RelayCommand] public void PageChanged() { if (PageReset) { PageReset = false; return; } GamesPaged.Clear(); LoadingStart(); for (int i = ((252 * (CurrentPage))); i < (252 * (CurrentPage + 1)); i++) { if (Games.Count > i) { GamesPaged.Add(Games[i]); } } LoadingEnd(); } public void LoadingStart() { LoadingSize = 200; GamesListHeight = new GridLength(0, GridUnitType.Star); LoadingHeight = new GridLength(1, GridUnitType.Star); } public void LoadingEnd() { GamesListHeight = new GridLength(1, GridUnitType.Star); LoadingHeight = new GridLength(0, GridUnitType.Star); LoadingSize = 0; } public void CopyToClipboard(string index) { var titleid = GamesResponse.Titles[int.Parse(index)].TitleId.ToString(); var title = GamesResponse.Titles[int.Parse(index)].Name.ToString(); Clipboard.SetDataObject(GamesResponse.Titles[int.Parse(index)].TitleId.ToString()); _snackbarService.Show("TitleID Copied", $"Copied the title ID of {title.ToString()} to clipboard\nTitleID: {titleid.ToString()}", ControlAppearance.Success, new SymbolIcon(SymbolRegular.ClipboardCheckmark24), _snackbarDuration); } } } ================================================ FILE: XAU/ViewModels/Pages/HomeViewModel.cs ================================================ using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Security.Cryptography; using System.Text; using XAU.Util.Etw; using System.Windows.Media; using Wpf.Ui.Controls; using Memory; using System.Net.Http; using Newtonsoft.Json.Linq; using Newtonsoft.Json; using System.Net; using System.Collections.ObjectModel; using System.IO.Compression; using Wpf.Ui.Common; using Wpf.Ui.Contracts; using XboxAuthNet.OAuth; using XboxAuthNet.OAuth.CodeFlow; using XboxAuthNet.XboxLive; using XboxAuthNet.XboxLive.Requests; using XboxAuthNet.XboxLive.Responses; namespace XAU.ViewModels.Pages { public partial class ImageItem : ObservableObject { [ObservableProperty] private string _imageUrl; } public partial class HomeViewModel : ObservableObject, INavigationAware { public static string ToolVersion = "EmptyDevToolVersion"; public static string EventsVersion = "1.0"; //attach vars [ObservableProperty] private string _attached = "Not Attached"; [ObservableProperty] private Brush _attachedColor = new SolidColorBrush(Colors.Red); [ObservableProperty] private string _loggedIn = "Not Logged In"; [ObservableProperty] private Brush _loggedInColor = new SolidColorBrush(Colors.Red); //profile vars [ObservableProperty] private string? _gamerPic = "pack://application:,,,/Assets/cirno.png"; [ObservableProperty] private string? _gamerTag = "Gamertag: Unknown "; [ObservableProperty] private string? _xuid = "XUID: Unknown"; [ObservableProperty] private string? _gamerScore = "Gamerscore: Unknown"; [ObservableProperty] private string? _profileRep = "Reputation: Unknown"; [ObservableProperty] private string? _accountTier = "Tier: Unknown"; [ObservableProperty] private string? _currentlyPlaying = "Currently Playing: Unknown"; [ObservableProperty] private string? _activeDevice = "Active Device: Unknown"; [ObservableProperty] private string? _isVerified = "Verified: Unknown"; [ObservableProperty] private string? _location = "Location: Unknown"; [ObservableProperty] private string? _tenure = "Tenure: Unknown"; [ObservableProperty] private string? _following = "Following: Unknown"; [ObservableProperty] private string? _followers = "Followers: Unknown"; [ObservableProperty] private string? _gamepass = "Gamepass: Unknown"; [ObservableProperty] private string? _bio = "Bio: Unknown"; [ObservableProperty] private string _loginText = "Login"; [ObservableProperty] public static bool _isLoggedIn = false; [ObservableProperty] public static bool _updateAvaliable = false; [ObservableProperty] private ObservableCollection _watermarks = new ObservableCollection(); private readonly Lazy _xboxRestAPI; private readonly Lazy _gitHubRestAPI = new Lazy(); public static int SpoofingStatus = 0; //0 = NotSpoofing, 1 = Spoofing, 2 = AutoSpoofing public static string SpoofedTitleID = "0"; public static string AutoSpoofedTitleID = "0"; //SnackBar public HomeViewModel(ISnackbarService snackbarService, IContentDialogService contentDialogService) { _snackbarService = snackbarService; _contentDialogService = contentDialogService; // Assume XAUTH and System Language are set by the time this is actually instantiated _xboxRestAPI = new Lazy(() => new XboxRestAPI(XAUTH)); } private readonly ISnackbarService _snackbarService; private TimeSpan _snackbarDuration = TimeSpan.FromSeconds(2); private readonly IContentDialogService _contentDialogService; private const string XAuthScanPattern = "58 42 4C 33 2E 30 20 78 3D"; [RelayCommand] private void RefreshProfile() { GrabProfile(); } Mem m = new Mem(); public BackgroundWorker XauthWorker = new BackgroundWorker(); public BackgroundWorker EventsTokenWorker = new BackgroundWorker(); bool IsAttached = false; bool GrabbedProfile = false; bool eventsTokenFound = false; public static bool XAUTHTested = false; public static string XAUTH = ""; public static string XUIDOnly; public static bool InitComplete = false; private bool _isInitialized = false; string SettingsFilePath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU"), "settings.json"); string EventsMetaFilePath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU"), "Events", "meta.json"); string AuthFilePath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU"), "auth.json"); public CodeFlowAuthenticator oauth; public XboxAuthClient xboxAuthClient; public XboxSignedClient xboxSignedClient; public async void OnNavigatedTo() { if (!_isInitialized) await InitializeViewModel(); } public void OnNavigatedFrom() { } #region Update private async Task CheckForToolUpdates() { if (ToolVersion == "EmptyDevToolVersion") return; if (ToolVersion.Contains("DEV")) { var jsonResponse = await _gitHubRestAPI.Value.GetDevToolVersionAsync(); if (("DEV-" + jsonResponse.LatestBuildVersion.ToString()) != ToolVersion) { var result = await _contentDialogService.ShowSimpleDialogAsync( new SimpleContentDialogCreateOptions() { Title = $"Version {jsonResponse.LatestBuildVersion.ToString()} available to download", Content = "Would you like to update to this version?", PrimaryButtonText = "Update", CloseButtonText = "Cancel" } ); if (result == ContentDialogResult.Primary) { _snackbarService.Show("Downloading update...", "Please wait", ControlAppearance.Info, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration); string sourceFile = jsonResponse.DownloadURL.ToString(); string destFile = @"XAU-new.exe"; var fileDownloader = new FileDownloader(); await fileDownloader.DownloadFileAsync(new Uri(sourceFile).ToString(), destFile, UpdateTool); } } } else { var jsonResponse = await _gitHubRestAPI.Value.GetReleaseVersionAsync(); if (jsonResponse[0].tag_name.ToString() != ToolVersion) { var result = await _contentDialogService.ShowSimpleDialogAsync( new SimpleContentDialogCreateOptions() { Title = $"Version {jsonResponse[0].tag_name.ToString()} available to download", Content = "Would you like to update to this version?", PrimaryButtonText = "Update", CloseButtonText = "Cancel" } ); if (result == ContentDialogResult.Primary) { _snackbarService.Show("Downloading update...", "Please wait", ControlAppearance.Info, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration); string sourceFile = jsonResponse[0].assets[0].browser_download_url.ToString(); string destFile = @"XAU-new.exe"; var fileDownloader = new FileDownloader(); await fileDownloader.DownloadFileAsync(sourceFile, destFile, UpdateTool); } } } } private async void CheckForEventUpdates() { if (EventsVersion == "EmptyDevEventsVersion") return; var response = await _gitHubRestAPI.Value.CheckForEventUpdatesAsync(); var EventsTimestamp = 0; if (File.Exists(EventsMetaFilePath)) { var metaJson = File.ReadAllText(EventsMetaFilePath); var meta = JsonConvert.DeserializeObject(metaJson); EventsTimestamp = meta.Timestamp; } if (response.Timestamp > EventsTimestamp && response.DataVersion == EventsVersion) { _snackbarService.Show("Downloading Events Update...", "Please wait", ControlAppearance.Info, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration); UpdateEvents(); } } private void UpdateTool(object sender, AsyncCompletedEventArgs e) { var path = Environment.ProcessPath.ToString(); string[] splitpath = path.Split("\\"); using (StreamWriter writer = new StreamWriter("XAU-Updater.bat")) { writer.WriteLine("@echo off"); writer.WriteLine("timeout 1 > nul"); writer.WriteLine("del \"" + Environment.ProcessPath + "\" "); writer.WriteLine("del \"" + splitpath[splitpath.Count() - 1] + "\" "); writer.WriteLine("ren XAU-new.exe \"" + splitpath[splitpath.Count() - 1] + "\" "); writer.WriteLine("start \"\" " + "\"" + splitpath[splitpath.Count() - 1] + "\""); writer.WriteLine("goto 2 > nul & del \"%~f0\""); } Process proc = new Process(); proc.StartInfo.FileName = "XAU-Updater.bat"; proc.StartInfo.WorkingDirectory = Environment.CurrentDirectory; proc.Start(); Environment.Exit(0); } private async void UpdateEvents() { string XAUPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU"); string backupFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU", "Events", "Backup"); Directory.CreateDirectory(backupFolderPath); string eventsFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU", "Events"); string[] eventFiles = Directory.GetFiles(eventsFolderPath); string[] backupFiles = Directory.GetFiles(backupFolderPath); foreach (string file in backupFiles) { File.Delete(file); } foreach (string eventFile in eventFiles) { string fileName = Path.GetFileName(eventFile); string destinationPath = Path.Combine(backupFolderPath, fileName); File.Move(eventFile, destinationPath, true); } string zipFilePath = Path.Combine(XAUPath, "Events.zip"); string extractPath = XAUPath; using (var client = new FileDownloader()) { await client.DownloadFileAsync(EventsUrls.Zip, zipFilePath); } ZipFile.ExtractToDirectory(zipFilePath, extractPath); File.Delete(zipFilePath); //download and place meta.json in the events folder string MetaFilePath = Path.Combine(eventsFolderPath, "meta.json"); using (var client = new FileDownloader()) { await client.DownloadFileAsync(EventsUrls.MetaUrl, MetaFilePath); } _snackbarService.Show("Events Update Complete", "Events have been updated to the latest version.", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration); } private async void CheckForXboxGamesDatabaseUpdate() { try { var fileInfo = await _gitHubRestAPI.Value.GetXboxGamesDatabaseInfoAsync(); if (fileInfo == null) { _snackbarService.Show("Error", "Could not check for database updates.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } string titleSearchPath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU"), "TitleSearch"); string shaFilePath = Path.Combine(titleSearchPath, "xbox_games_sha.txt"); string dbFilePath = Path.Combine(titleSearchPath, "xbox_games.db"); Directory.CreateDirectory(titleSearchPath); string currentSha = string.Empty; try { if (File.Exists(shaFilePath)) { currentSha = (await File.ReadAllTextAsync(shaFilePath)).Trim(); } } catch { } if (string.IsNullOrEmpty(currentSha) || !currentSha.Equals(fileInfo.Sha, StringComparison.OrdinalIgnoreCase)) { _snackbarService.Show("Database Update", "New Xbox games database available. Downloading...", ControlAppearance.Info, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration); using var client = new HttpClient(); var response = await client.GetAsync(fileInfo.DownloadUrl); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsByteArrayAsync(); await File.WriteAllBytesAsync(dbFilePath, content); try { await File.WriteAllTextAsync(shaFilePath, fileInfo.Sha); } catch (Exception ex) { Console.WriteLine($"Failed to store database SHA: {ex.Message}"); } _snackbarService.Show("Success", "Xbox games database updated successfully!", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration); } else { Console.WriteLine("Xbox games database is up to date."); } } catch (Exception ex) { _snackbarService.Show("Error", $"Database update check failed: {ex.Message}", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); } } #endregion private async Task InitializeViewModel() { await CheckForToolUpdates(); XauthWorker.DoWork += XauthWorker_DoWork; XauthWorker.ProgressChanged += XauthWorker_ProgressChanged; XauthWorker.RunWorkerCompleted += XauthWorker_RunWorkerCompleted; XauthWorker.WorkerReportsProgress = true; XauthWorker.RunWorkerAsync(); EventsTokenWorker.DoWork += EventsTokenWorker_DoWork; if (!File.Exists(SettingsFilePath)) { if (!Directory.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU"))) { Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU")); } if (!Directory.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU\\Events"))) { Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU\\Events")); } var defaultSettings = new XAUSettings { SettingsVersion = "2", ToolVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString(), UnlockAllEnabled = false, AutoSpooferEnabled = false, AutoLaunchXboxAppEnabled = false, FakeSignatureEnabled = true, RegionOverride = false, UseAcrylic = false, PrivacyMode = false, OAuthLogin = false }; string defaultSettingsJson = JsonConvert.SerializeObject(defaultSettings, Formatting.Indented); using (var file = new StreamWriter(SettingsFilePath)) { file.Write(defaultSettingsJson); } if (Settings.OAuthLogin) { OAuthLogin(); } } CheckForEventUpdates(); CheckForXboxGamesDatabaseUpdate(); LoadSettings(); if (Settings.OAuthLogin) OAuthLogin(); _isInitialized = true; if (Settings.AutoLaunchXboxAppEnabled && Process.GetProcessesByName(ProcessNames.XboxPcApp).Length == 0) { var p = new Process(); var startInfo = new ProcessStartInfo { UseShellExecute = true, FileName = @"shell:appsFolder\Microsoft.GamingApp_8wekyb3d8bbwe!Microsoft.Xbox.App" }; if (Settings.LaunchHidden) { startInfo.WindowStyle = ProcessWindowStyle.Hidden; } p.StartInfo = startInfo; p.Start(); } } #region Xauth public void XauthWorker_DoWork(object sender, DoWorkEventArgs e) { while (!Settings.OAuthLogin) { if (!m.OpenProcess((ProcessNames.XboxPcApp))) { IsAttached = false; Thread.Sleep(1000); } else { IsAttached = true; } Thread.Sleep(1000); XauthWorker.ReportProgress(0); } Thread.Sleep(5000); } public void XauthWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { if (IsAttached || XAUTH.Length > 0) { Attached = $"Attached to xbox app ({m.GetProcIdFromName(ProcessNames.XboxPcApp).ToString()})"; AttachedColor = new SolidColorBrush(Colors.Green); if (IsLoggedIn) { if (!GrabbedProfile) GrabProfile(); LoggedIn = "Logged In"; LoggedInColor = new SolidColorBrush(Colors.Green); } else { if (!SettingsViewModel.ManualXauth && !Settings.OAuthLogin) { GetXAUTH(); SettingsViewModel.ManualXauth = false; } LoggedIn = "Not Logged In"; LoggedInColor = new SolidColorBrush(Colors.Red); if (!XAUTHTested && XAUTH.Length > 0) { TestXAUTH(); } } } if (m.GetProcIdFromName(ProcessNames.XboxPcApp) == 0) { Attached = "Not Attached"; AttachedColor = new SolidColorBrush(Colors.Red); } } public void XauthWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (!XauthWorker.IsBusy) XauthWorker.RunWorkerAsync(); } private async void GetXAUTH() { IEnumerable XauthScanList = await m.AoBScan(XAuthScanPattern, true); string[] XauthStrings = new string[XauthScanList.Count()]; var i = 0; foreach (var address in XauthScanList) { XauthStrings[i] = m.ReadString(address.ToString("X"), length: 10000); i++; } Dictionary frequency = new Dictionary(); foreach (string str in XauthStrings) { if (!frequency.ContainsKey(str)) { frequency[str] = 1; } else { frequency[str]++; } } if (XauthStrings.Length == 0) { return; } string mostCommon = XauthStrings[0]; int highestFrequency = 0; foreach (KeyValuePair pair in frequency) { if (pair.Value > highestFrequency) { mostCommon = pair.Key; highestFrequency = pair.Value; } } if (highestFrequency > 3) { XAUTH = mostCommon; XAUTHTested = false; } } private async void TestXAUTH() { try { var response = await _xboxRestAPI.Value.GetBasicProfileAsync(); if (Settings.PrivacyMode) { GamerTag = $"Gamertag: Hidden"; Xuid = $"XUID: Hidden"; } else { GamerTag = $"Gamertag: {response.ProfileUsers[0].Settings[0].Value}"; Xuid = $"XUID: {response.ProfileUsers[0].Id}"; } XUIDOnly = response.ProfileUsers[0].Id; IsLoggedIn = true; XAUTHTested = true; InitComplete = true; // Start the events token worker to periodically check/refresh the token if (Settings.AutoGrabEventsToken && !EventsTokenWorker.IsBusy) EventsTokenWorker.RunWorkerAsync(); } catch (HttpRequestException ex) { if (ex.StatusCode == HttpStatusCode.Unauthorized) { IsLoggedIn = false; XAUTHTested = true; } } } #endregion #region EventsToken private bool solitaireLaunchedByUs = false; private static readonly TimeSpan EventsTokenCheckInterval = TimeSpan.FromMinutes(10); private static readonly TimeSpan EventsTokenMaxAge = TimeSpan.FromHours(23); private static DateTime _eventsTokenObtainedAt = DateTime.MinValue; private static string _eventsUserHash = null; private static readonly string EventsLogPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU", "events_debug.log"); public static void EventsLog(string msg) { var line = $"[{DateTime.Now:HH:mm:ss}] {msg}"; try { Directory.CreateDirectory(Path.GetDirectoryName(EventsLogPath)!); File.AppendAllText(EventsLogPath, line + Environment.NewLine); } catch { } } public void PersistEventsToken() { try { Settings.CachedEventsToken = AchievementsViewModel.EventsToken; Settings.EventsTokenObtainedAt = _eventsTokenObtainedAt; Settings.EventsUserHash = _eventsUserHash; var json = JsonConvert.SerializeObject(Settings); File.WriteAllText(SettingsFilePath, json); } catch { } } public void EventsTokenWorker_DoWork(object sender, DoWorkEventArgs e) { try { EventsTokenWorkerLoop(); } catch (Exception ex) { EventsLog($"Worker crashed: {ex.Message}"); } } private void EventsTokenWorkerLoop() { EventsLog("Worker started"); // Wait for login before scanning while (!IsLoggedIn) { Thread.Sleep(2000); } EventsLog("Logged in, entering refresh loop"); // If a token already exists (e.g. from OAuth or cache), mark it as fresh if (!string.IsNullOrEmpty(AchievementsViewModel.EventsToken) && _eventsTokenObtainedAt == DateTime.MinValue) _eventsTokenObtainedAt = DateTime.UtcNow; while (true) { if (!Settings.AutoGrabEventsToken || !IsLoggedIn) { Thread.Sleep(5000); continue; } var currentToken = AchievementsViewModel.EventsToken; bool isEmpty = string.IsNullOrEmpty(currentToken); bool isValid = !isEmpty && IsEventsTokenValid(); var tokenAge = DateTime.UtcNow - _eventsTokenObtainedAt; bool isExpired = !isEmpty && isValid && tokenAge > EventsTokenMaxAge; EventsLog($"Check: empty={isEmpty}, valid={isValid}, age={tokenAge.TotalMinutes:F0}m, expired={isExpired}"); if (isEmpty || !isValid || isExpired) { if (isExpired) EventsLog($"Token expired (age: {tokenAge.TotalMinutes:F0}m > {EventsTokenMaxAge.TotalMinutes:F0}m), refreshing..."); else EventsLog("Token missing/invalid, refreshing..."); // Keep capturing until we get a token or settings change while (Settings.AutoGrabEventsToken && IsLoggedIn) { var token = EtwTokenCapture.Capture(20); if (!string.IsNullOrEmpty(token)) { AchievementsViewModel.EventsToken = token; _eventsTokenObtainedAt = DateTime.UtcNow; PersistEventsToken(); EventsLog("ETW capture success."); break; } EventsLog("ETW capture found no token, retrying in 5s..."); Thread.Sleep(5000); } } EventsLog($"Sleeping {EventsTokenCheckInterval.TotalMinutes:F0}m..."); Thread.Sleep(EventsTokenCheckInterval); } } /// /// Launches Solitaire (if needed), then continuously captures ETW network traffic /// and scans for the events token until one is found or Solitaire exits. /// private void GrabEventsTokenFromSolitaire() { bool alreadyRunning = Process.GetProcessesByName(ProcessNames.Solitaire).Length > 0; EventsLog($"Solitaire already running: {alreadyRunning}"); // Start ETW capture FIRST — the token is sent in the initial telemetry burst // when Solitaire contacts Xbox Live, which happens within seconds of launch. EventsLog("Starting ETW before Solitaire launch..."); EtwTokenCapture.Cleanup(); string method = EtwTokenCapture.Start(); if (method == null) { EventsLog("Failed to start ETW trace (not running as admin?)"); return; } EventsLog($"ETW started via {method}"); if (!alreadyRunning) { try { EventsLog("Launching Solitaire..."); var p = new Process(); p.StartInfo = new ProcessStartInfo { UseShellExecute = true, FileName = AppLaunchUris.Solitaire }; p.Start(); solitaireLaunchedByUs = true; } catch (Exception ex) { EventsLog($"Failed to launch Solitaire: {ex.Message}"); EtwTokenCapture.Stop(method); EtwTokenCapture.CleanupFiles(); return; } // Wait for the process to appear for (int i = 0; i < 15; i++) { Thread.Sleep(1000); if (Process.GetProcessesByName(ProcessNames.Solitaire).Length > 0) { EventsLog($"Solitaire process appeared after {i + 1}s"); break; } } if (Process.GetProcessesByName(ProcessNames.Solitaire).Length == 0) { EventsLog("Solitaire never appeared after 15s"); solitaireLaunchedByUs = false; EtwTokenCapture.Stop(method); EtwTokenCapture.CleanupFiles(); return; } } // Wait for Xbox Live init + initial telemetry burst (token is sent here) EventsLog("Waiting 25s for Xbox Live init + telemetry events..."); Thread.Sleep(25000); // Stop first capture and try to extract EtwTokenCapture.Stop(method); Thread.Sleep(2000); string token = EtwTokenCapture.ExtractTokens(); EtwTokenCapture.CleanupFiles(); if (!string.IsNullOrEmpty(token)) { EventsLog($"ETW capture success on initial capture, len={token.Length}"); AchievementsViewModel.EventsToken = token; _eventsTokenObtainedAt = DateTime.UtcNow; eventsTokenFound = true; } else { // Loop: keep capturing while Solitaire is running EventsLog("Initial capture found nothing, entering continuous scan loop..."); int attempt = 0; while (!eventsTokenFound && Process.GetProcessesByName(ProcessNames.Solitaire).Length > 0) { attempt++; EventsLog($"Capture attempt {attempt}..."); token = EtwTokenCapture.Capture(20); if (!string.IsNullOrEmpty(token)) { EventsLog($"ETW capture success on attempt {attempt}, len={token.Length}"); AchievementsViewModel.EventsToken = token; _eventsTokenObtainedAt = DateTime.UtcNow; eventsTokenFound = true; break; } // Brief pause before next capture Thread.Sleep(3000); } if (!eventsTokenFound) EventsLog("Solitaire exited before token was found"); } // Close Solitaire if we launched it if (solitaireLaunchedByUs) { try { foreach (var proc in Process.GetProcessesByName(ProcessNames.Solitaire)) proc.Kill(); } catch { } solitaireLaunchedByUs = false; } } /// /// Manually triggers a scan (from the "Manually Refresh Token" button). /// Works regardless of the auto-grab setting. /// public bool ManualScanRunning { get; private set; } public void ScanForEventsTokenManual() { eventsTokenFound = false; AchievementsViewModel.EventsToken = null; _eventsTokenObtainedAt = DateTime.MinValue; ManualScanRunning = true; System.Threading.Tasks.Task.Run(() => { try { GrabEventsTokenFromSolitaire(); if (!string.IsNullOrEmpty(AchievementsViewModel.EventsToken)) { _eventsTokenObtainedAt = DateTime.UtcNow; PersistEventsToken(); } } finally { ManualScanRunning = false; } }); } /// /// Returns whether the current events token looks structurally valid. /// public static bool IsEventsTokenValid() { var token = AchievementsViewModel.EventsToken; return !string.IsNullOrWhiteSpace(token) && token.StartsWith("x:XBL3.0 x=") && token.Length > 30; } /// /// Returns whether the current events token has exceeded its max age. /// public static bool IsEventsTokenExpired() { if (_eventsTokenObtainedAt == DateTime.MinValue) return false; // no timestamp means we can't determine expiry return (DateTime.UtcNow - _eventsTokenObtainedAt) > EventsTokenMaxAge; } /// /// The UTC time the current events token was obtained. /// public static DateTime EventsTokenObtainedAtUtc => _eventsTokenObtainedAt; /// /// The UTC time the current events token is expected to expire. /// public static DateTime? EventsTokenExpiresAtUtc => _eventsTokenObtainedAt == DateTime.MinValue ? null : _eventsTokenObtainedAt + EventsTokenMaxAge; #endregion #region OAuthLogin [RelayCommand] private async void OAuthLogin() { //init oauth stuff var OAuthhttpClient = new HttpClient(); var apiClient = new CodeFlowLiveApiClient(XboxGameTitles.XboxAppPC, XboxAuthConstants.XboxScope, OAuthhttpClient); xboxAuthClient = new XboxAuthClient(OAuthhttpClient); xboxSignedClient = new XboxSignedClient(OAuthhttpClient); oauth = new CodeFlowBuilder(apiClient) .WithUIParent(this) .Build(); if (LoginText == "Logout") { oauth.Signout(); try { File.Delete(AuthFilePath); } catch { } ClearProfileState(); LoginText = "Login"; return; } Settings.OAuthLogin = true; // Use saved session if valid; otherwise interactive login MicrosoftOAuthResponse? response = await TryRestoreSessionAsync(); if (response == null) response = await TryInteractiveLoginAsync(); } private void DeleteAuthFile() { try { File.Delete(AuthFilePath); } catch { } } private void CompleteLogin(MicrosoftOAuthResponse response, string? successMessage = null) { writeSession(response); if (!string.IsNullOrEmpty(successMessage)) _snackbarService.Show("Success", successMessage, ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration); GenerateTokens(response); } private async Task TryRestoreSessionAsync() { if (!File.Exists(AuthFilePath)) return null; MicrosoftOAuthResponse? response; try { response = readSession(); } catch { DeleteAuthFile(); _snackbarService.Show("Session invalid", "Saved session could not be read. Please log in again.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return null; } if (response == null || !response.Validate() || string.IsNullOrEmpty(response.RefreshToken)) { DeleteAuthFile(); if (response != null && !response.Validate()) _snackbarService.Show("Session expired", "Your saved session has expired. Please log in again.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return null; } try { response = await oauth.AuthenticateSilently(response.RefreshToken!); CompleteLogin(response, "Logged in with previous session"); return response; } catch { _snackbarService.Show("Session invalid", "You are required to log in again as the session has expired", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); ClearProfileState(); return await TryInteractiveLoginAsync(); } } private async Task TryInteractiveLoginAsync() { try { var response = await oauth.AuthenticateInteractively(); CompleteLogin(response, "Logged in"); return response; } catch { _snackbarService.Show("Error", "Failed to authenticate", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return null; } } private async void GenerateTokens(MicrosoftOAuthResponse response) { var deviceToken = await xboxSignedClient.RequestDeviceToken(XboxDeviceTypes.Win32, "0.0.0"); var sisuResult = await xboxSignedClient.SisuAuth(new XboxSisuAuthRequest { AccessToken = response.AccessToken, ClientId = XboxGameTitles.XboxAppPC, DeviceToken = deviceToken.Token, RelyingParty = XboxAuthConstants.XboxLiveRelyingParty, }); try { XAUTH = $"XBL3.0 x={sisuResult.AuthorizationToken.XuiClaims.UserHash};{sisuResult.AuthorizationToken.Token}"; var xui = sisuResult.AuthorizationToken.XuiClaims; XUIDOnly = xui?.XboxUserId ?? ""; if (!string.IsNullOrEmpty(XUIDOnly)) { IsLoggedIn = true; XAUTHTested = true; InitComplete = true; if (Settings.PrivacyMode) { GamerTag = "Gamertag: Hidden"; Xuid = "XUID: Hidden"; } else { GamerTag = $"Gamertag: {xui?.Gamertag ?? "Unknown"}"; Xuid = $"XUID: {XUIDOnly}"; } } } catch { _snackbarService.Show("Error", "Failed to generate XAUTH", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); } LoginText = "Logout"; XauthWorker_ProgressChanged(null, null); if (IsLoggedIn && !GrabbedProfile) GrabProfile(); // Start the events token worker to periodically check/refresh the token if (Settings.AutoGrabEventsToken && !EventsTokenWorker.IsBusy) EventsTokenWorker.RunWorkerAsync(); } private void ClearProfileState() { IsLoggedIn = false; XAUTHTested = false; GrabbedProfile = false; XAUTH = ""; XUIDOnly = ""; GamerTag = "Gamertag: Unknown "; Xuid = "XUID: Unknown"; GamerPic = "pack://application:,,,/Assets/cirno.png"; GamerScore = "Gamerscore: Unknown"; ProfileRep = "Reputation: Unknown"; AccountTier = "Tier: Unknown"; CurrentlyPlaying = "Currently Playing: Unknown"; ActiveDevice = "Active Device: Unknown"; IsVerified = "Verified: Unknown"; Location = "Location: Unknown"; Tenure = "Tenure: Unknown"; Following = "Following: Unknown"; Followers = "Followers: Unknown"; Gamepass = "Gamepass: Unknown"; Bio = "Bio: Unknown"; Watermarks.Clear(); AchievementsViewModel.EventsToken = null; _eventsTokenObtainedAt = DateTime.MinValue; _eventsUserHash = null; XauthWorker_ProgressChanged(null, null); } private static readonly byte[] AuthFileMagic = Encoding.ASCII.GetBytes("XAU1"); private static readonly byte[] AuthDpapiEntropy = Encoding.UTF8.GetBytes("XAU-Auth-v1"); private MicrosoftOAuthResponse readSession() { var raw = File.ReadAllBytes(AuthFilePath); string json; if (raw.Length >= AuthFileMagic.Length && raw.AsSpan(0, AuthFileMagic.Length).SequenceEqual(AuthFileMagic)) { var encrypted = raw.AsSpan(AuthFileMagic.Length).ToArray(); var plain = ProtectedData.Unprotect(encrypted, AuthDpapiEntropy, DataProtectionScope.CurrentUser); json = Encoding.UTF8.GetString(plain); } else { json = Encoding.UTF8.GetString(raw); } var response = JsonConvert.DeserializeObject(json); return response; } private void writeSession(MicrosoftOAuthResponse response) { var json = JsonConvert.SerializeObject(response); var plain = Encoding.UTF8.GetBytes(json); var encrypted = ProtectedData.Protect(plain, AuthDpapiEntropy, DataProtectionScope.CurrentUser); using (var fs = new FileStream(AuthFilePath, FileMode.Create, FileAccess.Write, FileShare.None)) { fs.Write(AuthFileMagic, 0, AuthFileMagic.Length); fs.Write(encrypted, 0, encrypted.Length); } } #endregion #region Profile private async void GrabProfile() { try { var profileResponse = await _xboxRestAPI.Value.GetProfileAsync(XUIDOnly); if (profileResponse?.People?.Any() != true) { _snackbarService.Show("Error", "Failed to grab profile information.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } var person = profileResponse.People.FirstOrDefault(); if (Settings.PrivacyMode) { // Display hidden profile details for privacy mode GamerTag = "Gamertag: Hidden"; Xuid = "XUID: Hidden"; GamerPic = "pack://application:,,,/Assets/cirno.png"; GamerScore = "Gamerscore: Hidden"; ProfileRep = "Reputation: Hidden"; AccountTier = "Tier: Hidden"; CurrentlyPlaying = "Currently Playing: Hidden"; ActiveDevice = "Active Device: Hidden"; IsVerified = "Verified: Hidden"; Location = "Location: Hidden"; Tenure = "Tenure: Hidden"; Following = "Following: Hidden"; Followers = "Followers: Hidden"; Gamepass = "Gamepass: Hidden"; Bio = "Bio: Hidden"; } else { // Populate user profile details GamerTag = $"Gamertag: {person?.Gamertag ?? "Unknown"}"; Xuid = $"XUID: {person?.Xuid ?? "Unknown"}"; GamerPic = (person?.DisplayPicRaw?.Replace("&mode=Padding", "")) ?? "pack://application:,,,/Assets/default.png"; GamerScore = $"Gamerscore: {person?.GamerScore ?? "Unknown"}"; ProfileRep = $"Reputation: {person?.XboxOneRep ?? "Unknown"}"; AccountTier = $"Tier: {person?.Detail?.AccountTier ?? "Unknown"}"; // Currently playing information var presence = person?.PresenceDetails?.FirstOrDefault(); if (presence?.TitleId == null) { CurrentlyPlaying = "Currently Playing: Unknown (No Presence)"; } else { var gameTitle = await _xboxRestAPI.Value.GetGameTitleAsync(XUIDOnly, presence.TitleId); CurrentlyPlaying = gameTitle?.Titles?.FirstOrDefault()?.Name ?? $"Currently Playing: Unknown ({presence.TitleId})"; } // Retrieve Gamepass Membership Information try { var gpuResponse = await _xboxRestAPI.Value.GetGamepassMembershipAsync(XUIDOnly); Gamepass = $"Gamepass: {gpuResponse?.GamepassMembership ?? gpuResponse?.Data?.GamepassMembership ?? "Unknown"}"; } catch { Gamepass = "Gamepass: Unknown"; } // Active Device Information ActiveDevice = $"Active Device: {presence?.Device ?? "Unknown"}"; // Detailed profile information if (person?.Detail != null) { IsVerified = $"Verified: {person.Detail.IsVerified}"; Location = $"Location: {person.Detail.Location ?? "Unknown"}"; Tenure = $"Tenure: {person.Detail.Tenure ?? "Unknown"}"; Following = $"Following: {person.Detail.FollowingCount}"; Followers = $"Followers: {person.Detail.FollowerCount}"; Bio = $"Bio: {person.Detail.Bio ?? "No Bio"}"; // Handle Watermarks Watermarks.Clear(); // Parse Tenure Badge if (int.TryParse(person.Detail.Tenure, out int tenureInt)) { string tenureBadge = tenureInt.ToString("D2"); Watermarks.Add(new ImageItem { ImageUrl = $@"{BasicXboxAPIUris.WatermarksUrl}tenure/{tenureBadge}.png" }); } else { Console.WriteLine("The tenure string is not a valid integer."); } // Add Launch Watermarks if (person.Detail.Watermarks != null) { foreach (var watermark in person.Detail.Watermarks) { Watermarks.Add(new ImageItem { ImageUrl = $@"{BasicXboxAPIUris.WatermarksUrl}launch/{watermark.ToLower()}.png" }); } } } } GrabbedProfile = true; _snackbarService.Show("Success", "Profile information grabbed.", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration); } catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized) { IsLoggedIn = false; XAUTHTested = true; _snackbarService.Show("401 Unauthorized", "Something went wrong. Retrying.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); } catch (Exception ex) { _snackbarService.Show("Error", "Failed to grab profile information. " + ex.Message, ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); } } #endregion #region Settings public static XAUSettings Settings = new(); private void LoadSettings() { var settingsJson = File.ReadAllText(SettingsFilePath); var settings = JsonConvert.DeserializeObject(settingsJson); if (settings == null) { _snackbarService.Show( "Error", "Couldn't load settings.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24) ); return; } Settings.SettingsVersion = settings.SettingsVersion; Settings.ToolVersion = settings.ToolVersion; Settings.UnlockAllEnabled = settings.UnlockAllEnabled; Settings.AutoSpooferEnabled = settings.AutoSpooferEnabled; Settings.AutoLaunchXboxAppEnabled = settings.AutoLaunchXboxAppEnabled; Settings.LaunchHidden = settings.LaunchHidden; Settings.FakeSignatureEnabled = settings.FakeSignatureEnabled; Settings.RegionOverride = settings.RegionOverride; Settings.UseAcrylic = settings.UseAcrylic; Settings.PrivacyMode = settings.PrivacyMode; Settings.OAuthLogin = settings.OAuthLogin; Settings.AutoGrabEventsToken = settings.AutoGrabEventsToken; Settings.CachedEventsToken = settings.CachedEventsToken; Settings.EventsTokenObtainedAt = settings.EventsTokenObtainedAt; Settings.EventsUserHash = settings.EventsUserHash; _eventsUserHash = settings.EventsUserHash; // Restore cached events token if it's still fresh if (!string.IsNullOrEmpty(settings.CachedEventsToken) && settings.EventsTokenObtainedAt.HasValue) { var age = DateTime.UtcNow - settings.EventsTokenObtainedAt.Value; if (age < EventsTokenMaxAge) { AchievementsViewModel.EventsToken = settings.CachedEventsToken; _eventsTokenObtainedAt = settings.EventsTokenObtainedAt.Value; EventsLog($"Restored cached events token (age: {age.TotalHours:F1}h)"); } else { EventsLog($"Cached events token expired (age: {age.TotalHours:F1}h), will re-grab"); } } } #endregion } } ================================================ FILE: XAU/ViewModels/Pages/InfoViewModel.cs ================================================ using Wpf.Ui.Controls; namespace XAU.ViewModels.Pages { public partial class InfoViewModel : ObservableObject, INavigationAware { private bool _isInitialized = false; [ObservableProperty] private string? _toolVersion; public void OnNavigatedTo() { if (!_isInitialized) InitializeViewModel(); } public void OnNavigatedFrom() { } private void InitializeViewModel() { ToolVersion = $"Version: {HomeViewModel.ToolVersion}"; _isInitialized = true; } [RelayCommand] public void OpenDiscordUrl(string url) { var destinationurl = OpenableLinks.Discord; var sInfo = new System.Diagnostics.ProcessStartInfo(destinationurl) { UseShellExecute = true, }; System.Diagnostics.Process.Start(sInfo); } [RelayCommand] public void OpenGithubUserUrl(string url) { var destinationurl = OpenableLinks.GitHubUserUrl; var sInfo = new System.Diagnostics.ProcessStartInfo(destinationurl) { UseShellExecute = true, }; System.Diagnostics.Process.Start(sInfo); } } } ================================================ FILE: XAU/ViewModels/Pages/MiscViewModel.cs ================================================ using HtmlAgilityPack; using Microsoft.Data.Sqlite; using Newtonsoft.Json.Linq; using System.Data; using System.Diagnostics; using System.DirectoryServices; using System.IO; using System.Text; using System.Windows.Input; using Wpf.Ui.Common; using Wpf.Ui.Contracts; using Wpf.Ui.Controls; using Wpf.Ui.Services; namespace XAU.ViewModels.Pages { public partial class MiscViewModel : ObservableObject, INavigationAware { private readonly IContentDialogService _contentDialogService; private readonly ISnackbarService _snackbarService; private TimeSpan _snackbarDuration = TimeSpan.FromSeconds(2); private Lazy _xboxRestAPI = new Lazy(() => new XboxRestAPI(HomeViewModel.XAUTH)); public MiscViewModel(ISnackbarService snackbarService) { _snackbarService = snackbarService; _contentDialogService = new ContentDialogService(); } public void OnNavigatedTo() { if (!IsInitialized && HomeViewModel.InitComplete) InitializeViewModel(); } public void OnNavigatedFrom() { } private void InitializeViewModel() { IsInitialized = true; } #region Spoofer [ObservableProperty] private string _gameName = "Name: "; [ObservableProperty] private string _gameTitleID = "Title ID: "; [ObservableProperty] private string _gamePFN = "PFN: "; [ObservableProperty] private string _gameType = "Type: "; [ObservableProperty] private string _gameGamepass = "Gamepass: "; [ObservableProperty] private string _gameDevices = "Devices: "; [ObservableProperty] private string _gameGamerscore = "Gamerscore: ?/?"; [ObservableProperty] private string? _gameImage = "pack://application:,,,/Assets/cirno.png"; [ObservableProperty] private string _gameTime = "Time Played: "; [ObservableProperty] private bool _isInitialized = false; [ObservableProperty] private string _currentSpoofingID = ""; [ObservableProperty] private string _newSpoofingID = ""; [ObservableProperty] private string _spoofingText = "Spoofing Not Started"; [ObservableProperty] private string _spoofingButtonText = "Start Spoofing"; private bool SpoofingUpdate = false; private bool CurrentlySpoofing = false; private GameTitle GameInfoResponse; private GameStatsResponse GameStatsResponse; [RelayCommand] public async Task SpooferButtonClicked() { if (CurrentlySpoofing) { SpoofingUpdate = true; CurrentlySpoofing = false; SpoofingText = "Spoofing Not Started"; SpoofingButtonText = "Start Spoofing"; //reset game info GameName = "Name: "; GameTitleID = "Title ID: "; GamePFN = "PFN: "; GameType = "Type: "; GameGamepass = "Gamepass: "; GameDevices = "Devices: "; GameGamerscore = "Gamerscore: ?/?"; GameImage = "pack://application:,,,/Assets/cirno.png"; GameTime = "Time Played: "; HomeViewModel.SpoofingStatus = 0; await _xboxRestAPI.Value.StopHeartbeatAsync(HomeViewModel.XUIDOnly); return; } HomeViewModel.SpoofedTitleID = NewSpoofingID; if (HomeViewModel.SpoofingStatus == 2) { HomeViewModel.SpoofingStatus = 1; AchievementsViewModel.SpoofingUpdate = true; } HomeViewModel.SpoofingStatus = 1; SpoofGame(); } public async void SpoofGame() { CurrentSpoofingID = NewSpoofingID; GameInfoResponse = await _xboxRestAPI.Value.GetGameTitleAsync(HomeViewModel.XUIDOnly, NewSpoofingID); GameStatsResponse = await _xboxRestAPI.Value.GetGameStatsAsync(HomeViewModel.XUIDOnly, NewSpoofingID); if (GameInfoResponse == null || GameStatsResponse == null || !GameInfoResponse.Titles.Any()) { _snackbarService.Show("Error: Unable to acquire game info or stats", $"The game info was invalid.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } try { GameName = "Name: " + GameInfoResponse.Titles[0].Name; GameImage = !string.IsNullOrEmpty(GameInfoResponse.Titles[0].DisplayImage.ToString()) ? GameInfoResponse.Titles[0].DisplayImage.ToString() : "pack://application:,,,/Assets/cirno.png"; GameTitleID = "Title ID: " + GameInfoResponse.Titles[0].TitleId; GamePFN = "PFN: " + GameInfoResponse.Titles[0].Pfn; GameType = "Type: " + GameInfoResponse.Titles[0].Type; GameGamepass = "Gamepass: " + GameInfoResponse.Titles[0].GamePass?.IsGamePass; GameDevices = "Devices: "; foreach (var device in GameInfoResponse.Titles[0].Devices) { GameDevices += device.ToString() + ", "; } GameDevices = GameDevices.Remove(GameDevices.Length - 2); GameGamerscore = "Gamerscore: " + GameInfoResponse.Titles[0].Achievement?.CurrentGamerscore.ToString() + "/" + GameInfoResponse.Titles[0].Achievement?.TotalGamerscore.ToString(); try { var timePlayed = TimeSpan.FromMinutes(Convert.ToDouble(GameStatsResponse.StatListsCollection[0].Stats[0].Value)); var formattedTime = $"{timePlayed.Days} Days, {timePlayed.Hours} Hours and {timePlayed.Minutes} minutes"; GameTime = "Time Played: " + formattedTime; } catch { GameTime = "Time Played: Unknown"; } } catch { GameName = "Name: "; _snackbarService.Show("Error: Invalid TitleID", $"The TitleID entered is invalid or does not return information from the API", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } SpoofingUpdate = true; CurrentlySpoofing = true; SpoofingButtonText = "Stop Spoofing"; SpoofingText = $"Spoofing {GameInfoResponse.Titles[0].Name}"; await Task.Run(() => Spoofing()); } // TODO: this code seems like it's duplicated in AchievementsViewModel.cs too. public async Task Spoofing() { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); TimeSpan spoofingTime = stopwatch.Elapsed; SpoofingText = $"Spoofing {GameName} For: {spoofingTime.ToString(@"hh\:mm\:ss")}"; await _xboxRestAPI.Value.SendHeartbeatAsync(HomeViewModel.XUIDOnly, CurrentSpoofingID); var i = 0; Thread.Sleep(1000); SpoofingUpdate = false; while (!SpoofingUpdate) { if (i == 300) { await _xboxRestAPI.Value.SendHeartbeatAsync(HomeViewModel.XUIDOnly, CurrentSpoofingID); i = 0; } else { if (SpoofingUpdate) { HomeViewModel.SpoofingStatus = 0; HomeViewModel.SpoofedTitleID = "0"; break; } spoofingTime = stopwatch.Elapsed; SpoofingText = $"Spoofing {GameInfoResponse.Titles[0].Name} For: {spoofingTime.ToString(@"hh\:mm\:ss")}"; i++; } Thread.Sleep(1000); } } #endregion #region GameSearch [ObservableProperty] private List _tSearchResults = new List(); [ObservableProperty] private List _tSearchTitleNames = new List(); [ObservableProperty] private string _tSearchText = ""; [ObservableProperty] private string _tSearchGameName = "Name: "; [ObservableProperty] private string _tSearchGameTitleID = ""; [ObservableProperty] private string _tSearchGameTitleBased = "Title Based: Unknown"; private string GetDatabasePath() { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU", "TitleSearch", "xbox_games.db"); } [RelayCommand] public async Task SearchGame() { try { if (string.IsNullOrWhiteSpace(TSearchText)) { TSearchTitleNames = new List(); TSearchResults = new List(); return; } string dbPath = GetDatabasePath(); if (!File.Exists(dbPath)) { _snackbarService.Show("Error", "Game database not found. Please wait for it to download.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } var results = await Task.Run(() => SearchGamesInDatabase(dbPath, TSearchText)); if (!results.Any()) { _snackbarService.Show("Error", $"No results were found for '{TSearchText}'", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); TSearchTitleNames = new List(); TSearchResults = new List(); return; } results = results.OrderBy(game => game.Title, StringComparer.OrdinalIgnoreCase).ToList(); TSearchResults = results; TSearchTitleNames = results.Select(game => game.Title).ToList(); } catch (Exception ex) { _snackbarService.Show("Error", $"Search failed: {ex.Message}", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); TSearchTitleNames = new List(); TSearchResults = new List(); } } private List SearchGamesInDatabase(string dbPath, string searchText) { var results = new List(); using var connection = new SqliteConnection($"Data Source={dbPath}"); connection.Open(); // Search for games that contain the search text (case-insensitive) string sql = @" SELECT title, titleId, isTitleBased FROM games WHERE title LIKE @searchText ORDER BY title COLLATE NOCASE LIMIT 100"; using var command = new SqliteCommand(sql, connection); command.Parameters.AddWithValue("@searchText", $"%{searchText}%"); using var reader = command.ExecuteReader(); while (reader.Read()) { results.Add(new GameItem { Title = reader.GetString("title"), TitleId = reader.GetString("titleId"), IsTitleBased = reader.GetInt32("isTitleBased") == 1 }); } return results; } public void DisplayGameInfo(int index) { try { if (TSearchResults == null || TSearchResults.Count <= index || index < 0) { _snackbarService.Show("Error", "No game found at the selected index.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } var selectedGame = TSearchResults[index]; TSearchGameName = selectedGame.Title; TSearchGameTitleID = selectedGame.TitleId; TSearchGameTitleBased = $"Title Based: {(selectedGame.IsTitleBased ? "True" : "False")}"; } catch (Exception ex) { _snackbarService.Show("Error", "Failed to display game info.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); } } #endregion #region GamertagSearch [ObservableProperty] private string _gamertag = ""; [ObservableProperty] private string _gamertagName = "Gamertag:"; [ObservableProperty] private string _gamertagImage = "pack://application:,,,/Assets/cirno.png"; [ObservableProperty] private string _gamertagScore = "Gamerscore: "; [ObservableProperty] private string _gamertagXuid; [ObservableProperty] private bool _excludeZeroGamerscoreGames; [ObservableProperty] private bool _excludeXbox360Games; [RelayCommand] public async Task SearchGamertag() { if (string.IsNullOrWhiteSpace(Gamertag)) { _snackbarService.Show("Error", "Please enter a valid gamertag.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } var profileData = await _xboxRestAPI.Value.GetGamertagProfileAsync(Gamertag) ?? new JObject(); var profileUsers = profileData["profileUsers"]?.FirstOrDefault(); if (profileUsers == null) { _snackbarService.Show("Error", "Failed to fetch gamertag information.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } GamertagXuid = profileUsers["id"]?.ToString() ?? string.Empty; GamertagName = "Gamertag: " + profileUsers["settings"]?.FirstOrDefault(setting => setting["id"]?.ToString() == "Gamertag")?["value"]?.ToString() ?? "Unknown"; GamertagScore = "Gamerscore: " + profileUsers["settings"]?.FirstOrDefault(setting => setting["id"]?.ToString() == "Gamerscore")?["value"]?.ToString() ?? "Unknown"; GamertagImage = profileUsers["settings"]?.FirstOrDefault(setting => setting["id"]?.ToString() == "GameDisplayPicRaw")?["value"]?.ToString()?.Replace("&mode=Padding", "") ?? string.Empty; } public async Task ExportToCsvAsync() { if (string.IsNullOrWhiteSpace(GamertagXuid)) { _snackbarService.Show("Error", "Search for a user first.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } try { _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); var gamesResponse = await _xboxRestAPI.Value.GetGamesListAsync(GamertagXuid); if (gamesResponse == null || gamesResponse.Titles == null) { await Task.Delay(2500); _snackbarService.Show("Error", "Failed to fetch games list.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); return; } if (gamesResponse.Titles.Count == 0) { await Task.Delay(2500); _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); return; } var sb = new StringBuilder(); sb.AppendLine("\"Title ID\",\"Title\",\"CurrentAchievements\",\"Gamerscore\",\"Progress\",\"Devices\",\"Genres\""); foreach (var title in gamesResponse.Titles) { if (ExcludeZeroGamerscoreGames && title.Achievement.TotalGamerscore == 0) { continue; } if (ExcludeXbox360Games && title.Devices != null && title.Devices.Contains("Xbox360")) { continue; } var titleName = title.Name.Replace("\"", "\"\""); var devices = title.Devices != null ? string.Join(", ", title.Devices).Replace("\"", "\"\"") : string.Empty; var genres = title.Detail?.Genres != null ? string.Join(", ", title.Detail.Genres).Replace("\"", "\"\"") : string.Empty; sb.AppendLine($"\"{title.TitleId}\",\"{titleName}\",\"{title.Achievement.CurrentAchievements}\",\"{title.Achievement.CurrentGamerscore}/{title.Achievement.TotalGamerscore}\",\"{title.Achievement.ProgressPercentage}\",\"{devices}\",\"{genres}\""); } var saveFileDialog = new Microsoft.Win32.SaveFileDialog { Filter = "CSV files (*.csv)|*.csv", FileName = $"{GamertagXuid}.csv" }; if (saveFileDialog.ShowDialog() == true) { await Task.Run(() => { File.WriteAllText(saveFileDialog.FileName, sb.ToString()); }); _snackbarService.Show("Success", "Games list exported successfully.", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration); } else { _snackbarService.Show("Cancelled", "Game export was not completed", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.Warning24), _snackbarDuration); } } catch (Exception ex) { _snackbarService.Show("Error", "Failed to export games list: " + ex.Message, ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration); } } #endregion } } ================================================ FILE: XAU/ViewModels/Pages/SettingsViewModel.cs ================================================ using Newtonsoft.Json; using System.Diagnostics; using System.IO; using Wpf.Ui.Controls; using XAU.Services.HttpServer; namespace XAU.ViewModels.Pages { public partial class SettingsViewModel : ObservableObject, INavigationAware, IDisposable { private bool _isInitialized = false; [ObservableProperty] private string _appVersion = String.Empty; static string ProgramFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "XAU"); string SettingsFilePath = Path.Combine(ProgramFolderPath, "settings.json"); //settings [ObservableProperty] private string _settingsVersion; [ObservableProperty] private string _toolVersion; [ObservableProperty] private bool _unlockAllEnabled; [ObservableProperty] private bool _autoSpooferEnabled; [ObservableProperty] private bool _autoLaunchXboxAppEnabled; [ObservableProperty] private bool _launchHidden; [ObservableProperty] private bool _fakeSignatureEnabled; [ObservableProperty] private bool _regionOverride; [ObservableProperty] private bool _useAcrylic; [ObservableProperty] private bool _privacyMode; [ObservableProperty] private bool _oAuthLogin; [ObservableProperty] private bool _autoGrabEventsToken; [ObservableProperty] private string _xauth; [ObservableProperty] private bool _serverEnabled; [ObservableProperty] private string _serverPort = "1337"; [ObservableProperty] private string _listeningAddress = "http://localhost:1337"; private HttpServer? _httpServer; private bool _disposed; public static bool ManualXauth = false; public RoutedEventHandler OnNavigatedToEvent = null!; [RelayCommand] public void SaveSettings() { var settings = new XAUSettings { SettingsVersion = SettingsVersion, ToolVersion = ToolVersion, UnlockAllEnabled = UnlockAllEnabled, AutoSpooferEnabled = AutoSpooferEnabled, AutoLaunchXboxAppEnabled = AutoLaunchXboxAppEnabled, LaunchHidden = LaunchHidden, FakeSignatureEnabled = FakeSignatureEnabled, RegionOverride = RegionOverride, UseAcrylic = UseAcrylic, PrivacyMode = PrivacyMode, OAuthLogin = OAuthLogin, AutoGrabEventsToken = AutoGrabEventsToken }; string settingsJson = JsonConvert.SerializeObject(settings); File.WriteAllText(SettingsFilePath, settingsJson); HomeViewModel.Settings = settings; // update ref } [RelayCommand] private void ToggleServer() { if (_httpServer == null) { var routes = Routes.GetRoutes( getXauthToken: () => HomeViewModel.XAUTH, getXboxRestAPI: () => new XboxRestAPI(HomeViewModel.XAUTH), getXUIDOnly: () => HomeViewModel.XUIDOnly ); _httpServer = new HttpServer(ServerPort, routes); } if (ServerEnabled) { _httpServer.Start(); UpdateListeningAddress(); } else { _httpServer.Stop(); ListeningAddress = $"http://localhost:{ServerPort}"; } // TO DO: SAVE SERVER ENABLED/DISABLED STATUS & PORT NUMBER //SaveSettings(); } [RelayCommand] public void UpdateServerPort() { if (_httpServer != null) { _httpServer.UpdatePort(ServerPort); UpdateListeningAddress(); } // TO DO: SAVE SERVER ENABLED/DISABLED STATUS & PORT NUMBER //SaveSettings(); } [RelayCommand] public void RestartAsAdmin() { if (_httpServer != null) { _httpServer.RestartAsAdmin(); } } [RelayCommand] private void OpenListeningAddress() { try { if (!string.IsNullOrWhiteSpace(ListeningAddress)) { Process.Start(new ProcessStartInfo { FileName = ListeningAddress, UseShellExecute = true }); } } catch (Exception ex) { Debug.WriteLine($"Failed to open address: {ex.Message}"); } } public void OnNavigatedTo() { if (!_isInitialized) { InitializeViewModel(); } OnNavigatedToEvent.Invoke(this, new RoutedEventArgs()); } public void OnNavigatedFrom() { } private void InitializeViewModel() { LoadSettings(); ToolVersion = $"XAU - {GetAssemblyVersion()}"; SettingsVersion = "2"; _isInitialized = true; if (_httpServer == null) { var routes = Routes.GetRoutes( getXauthToken: () => HomeViewModel.XAUTH, getXboxRestAPI: () => new XboxRestAPI(HomeViewModel.XAUTH), getXUIDOnly: () => HomeViewModel.XUIDOnly ); _httpServer = new HttpServer(ServerPort, routes); } ListeningAddress = $"http://localhost:{ServerPort}"; } public void LoadSettings() { SettingsVersion = HomeViewModel.Settings.SettingsVersion; ToolVersion = HomeViewModel.Settings.ToolVersion; UnlockAllEnabled = HomeViewModel.Settings.UnlockAllEnabled; AutoSpooferEnabled = HomeViewModel.Settings.AutoSpooferEnabled; AutoLaunchXboxAppEnabled = HomeViewModel.Settings.AutoLaunchXboxAppEnabled; LaunchHidden = HomeViewModel.Settings.LaunchHidden; FakeSignatureEnabled = HomeViewModel.Settings.FakeSignatureEnabled; RegionOverride = HomeViewModel.Settings.RegionOverride; UseAcrylic = HomeViewModel.Settings.UseAcrylic; PrivacyMode = HomeViewModel.Settings.PrivacyMode; Xauth = HomeViewModel.XAUTH; OAuthLogin = HomeViewModel.Settings.OAuthLogin; AutoGrabEventsToken = HomeViewModel.Settings.AutoGrabEventsToken; } private string GetAssemblyVersion() { return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? String.Empty; } private void UpdateListeningAddress() { if (_httpServer != null) { ListeningAddress = _httpServer.GetListeningAddress(); } } partial void OnServerPortChanged(string value) { if (_httpServer != null) { _httpServer.UpdatePort(value); UpdateListeningAddress(); } // TO DO: SAVE SERVER ENABLED/DISABLED STATUS & PORT NUMBER //SaveSettings(); } public void Dispose() { if (_disposed) return; if (_httpServer != null) { _httpServer.Dispose(); _httpServer = null; } _disposed = true; } } } ================================================ FILE: XAU/ViewModels/Pages/StatsViewModel.cs ================================================ using Wpf.Ui.Controls; namespace XAU.ViewModels.Pages { public partial class StatsViewModel : ObservableObject, INavigationAware { private bool _isInitialized = false; public void OnNavigatedTo() { if (!_isInitialized) InitializeViewModel(); } public void OnNavigatedFrom() { } private void InitializeViewModel() { _isInitialized = true; } } } ================================================ FILE: XAU/ViewModels/Windows/MainWindowViewModel.cs ================================================ using System.Collections.ObjectModel; using Wpf.Ui.Common; using Wpf.Ui.Contracts; using Wpf.Ui.Controls; namespace XAU.ViewModels.Windows { public partial class MainWindowViewModel : ObservableObject { private readonly IContentDialogService _contentDialogService; DateTime startTime = DateTime.Now; public MainWindowViewModel(IContentDialogService contentDialogService) { _contentDialogService = contentDialogService; } public async Task ShowErrorDialog(Exception exception) { TimeSpan uptime = DateTime.Now - startTime; string OutputString = $"Information\n" + $"=================================\n" + $"Tool Version: {System.Reflection.Assembly.GetExecutingAssembly().GetName().Version}\n" + $"System Version: {Environment.OSVersion.Version.ToString()}\n" + $"Tool Uptime: {uptime.ToString()}\n" + $"=================================\n" + $"Exception\n" + $"=================================\n" + $"```\n{exception.ToString()}\n```"; ContentDialogResult result = await _contentDialogService.ShowSimpleDialogAsync( new SimpleContentDialogCreateOptions() { Title = "Oopsie Woopsy Fucky Wucky", 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", PrimaryButtonText = "Copy Error", CloseButtonText = "Close", }); switch (result) { case ContentDialogResult.Primary: Clipboard.SetDataObject(OutputString); break; } } [ObservableProperty] private string _applicationTitle = "Xbox Achievement Unlocker"; [ObservableProperty] private ObservableCollection _menuItems = new() { new NavigationViewItem() { Content = "Home", Icon = new SymbolIcon { Symbol = SymbolRegular.Home24 }, TargetPageType = typeof(Views.Pages.HomePage) }, new NavigationViewItem() { Content = "Games", Icon = new SymbolIcon { Symbol = SymbolRegular.Games24 }, TargetPageType = typeof(Views.Pages.GamesPage) }, new NavigationViewItem() { Content = "Achievements", Icon = new SymbolIcon { Symbol = SymbolRegular.Trophy24 }, TargetPageType = typeof(Views.Pages.AchievementsPage) }, /*new NavigationViewItem() { Content = "Stats", Icon = new SymbolIcon { Symbol = SymbolRegular.DataHistogram24 }, TargetPageType = typeof(Views.Pages.StatsPage) },*/ new NavigationViewItem() { Content = "Misc", Icon = new SymbolIcon { Symbol = SymbolRegular.MoreCircle24 }, TargetPageType = typeof(Views.Pages.MiscPage) } #if DEBUG , new NavigationViewItem() { Content = "Debug", Icon = new SymbolIcon { Symbol = SymbolRegular.Bug24 }, TargetPageType = typeof(Views.Pages.DebugPage) } #endif }; [ObservableProperty] private ObservableCollection _footerMenuItems = new() { new NavigationViewItem() { Content = "Settings", Icon = new SymbolIcon { Symbol = SymbolRegular.Settings24 }, TargetPageType = typeof(Views.Pages.SettingsPage) }, new NavigationViewItem() { Content = "Info", Icon = new SymbolIcon { Symbol = SymbolRegular.Info24 }, TargetPageType = typeof(Views.Pages.InfoPage) } }; [ObservableProperty] private ObservableCollection _trayMenuItems = new() { new MenuItem { Header = "Home", Tag = "tray_home" } }; } } ================================================ FILE: XAU/Views/Pages/AchievementsPage.xaml ================================================  ================================================ FILE: XAU/Views/Pages/AchievementsPage.xaml.cs ================================================ using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using Wpf.Ui.Controls; using XAU.ViewModels.Pages; namespace XAU.Views.Pages { /// /// Interaction logic for AchievementsPage.xaml /// public partial class AchievementsPage : INavigableView { public AchievementsViewModel ViewModel { get; } public AchievementsPage(AchievementsViewModel viewModel) { ViewModel = viewModel; DataContext = this; InitializeComponent(); } private void UnlockButton(object sender, RoutedEventArgs e) { ButtonBase SelectedAchievement = sender as ButtonBase; ViewModel.UnlockAchievement(Convert.ToInt32(SelectedAchievement.Tag)); } private void FilterBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { } private async void SearchBox_OnKeyDownAsync(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) { //for some reason, the search text is not being updated when pressing enter ViewModel.SearchText = SearchBox.Text; await ViewModel.SearchAndFilterAchievements(); } } } } ================================================ FILE: XAU/Views/Pages/DebugPage.xaml ================================================  ================================================ FILE: XAU/Views/Pages/DebugPage.xaml.cs ================================================ using Wpf.Ui.Controls; using XAU.ViewModels.Pages; namespace XAU.Views.Pages { public partial class DebugPage : INavigableView { public DebugViewModel ViewModel { get; } public DebugPage(DebugViewModel viewModel) { ViewModel = viewModel; DataContext = this; InitializeComponent(); } } } ================================================ FILE: XAU/Views/Pages/GamesPage.xaml ================================================  ================================================ FILE: XAU/Views/Pages/GamesPage.xaml.cs ================================================ using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media.Imaging; using Wpf.Ui.Controls; using XAU.ViewModels.Pages; namespace XAU.Views.Pages { /// /// Interaction logic for GamesPage.xaml /// public partial class GamesPage : INavigableView { public GamesViewModel ViewModel { get; } public GamesPage(GamesViewModel viewModel) { ViewModel = viewModel; DataContext = this; InitializeComponent(); } private async void ButtonBase_OnClick(object sender, RoutedEventArgs e) { ButtonBase selectedGame = sender as ButtonBase; await ViewModel.OpenAchievements(selectedGame.Content.ToString()); } private void SearchBox_OnKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) { //for some reason, the search text is not being updated when pressing enter ViewModel.SearchText = SearchBox.Text; ViewModel.SearchAndFilterGames(); } } private void FilterBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { ViewModel.FilterGames(); } private void PageBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { ViewModel.PageChanged(); } private void ButtonBase_RightClick(object sender, MouseButtonEventArgs e) { ButtonBase selectedGame = sender as ButtonBase; ViewModel.CopyToClipboard(selectedGame.Content.ToString()); } private void Image_ImageFailed(object sender, RoutedEventArgs e) { if (sender is Wpf.Ui.Controls.Image uiImage) { uiImage.Source = new BitmapImage(new Uri("pack://application:,,,/Assets/cirno.png")); } } } } ================================================ FILE: XAU/Views/Pages/HomePage.xaml ================================================  ================================================ FILE: XAU/Views/Pages/HomePage.xaml.cs ================================================ using Wpf.Ui.Controls; using XAU.ViewModels.Pages; namespace XAU.Views.Pages { public partial class HomePage : INavigableView { public HomeViewModel ViewModel { get; } public HomePage(HomeViewModel viewModel) { ViewModel = viewModel; DataContext = this; InitializeComponent(); } } } ================================================ FILE: XAU/Views/Pages/InfoPage.xaml ================================================  Discord Server Github ================================================ FILE: XAU/Views/Pages/InfoPage.xaml.cs ================================================ using Wpf.Ui.Controls; using XAU.ViewModels.Pages; namespace XAU.Views.Pages { /// /// Interaction logic for InfoPage.xaml /// public partial class InfoPage : INavigableView { public InfoViewModel ViewModel { get; } public InfoPage(InfoViewModel viewModel) { ViewModel = viewModel; DataContext = this; InitializeComponent(); } } } ================================================ FILE: XAU/Views/Pages/MiscPage.xaml ================================================