jaydenmilne / steamsync

Tool to automatically add games from the Epic Games Launcher to Steam
GNU Affero General Public License v3.0
157 stars 17 forks source link

Make Xbox game detection and naming more robust #20

Closed jamierajewski closed 2 years ago

jamierajewski commented 2 years ago

Fixes #18 and #19

Originally, the code would attempt to pull up the MicrosoftGame.config file and get info from that, but if it was missing, it would attempt to look at the appxmanifest.xml and check a ton of random cases to determine if it was a game or not. This inevitably still missed some games (like in the case of #18 ).

This was a reasonable approach, but as seen in #19, even if a game had MicrosoftGame.config, it wasn't necessarily correct. From all the cases I've seen, appxmanifest.xml is always(?) correct, it is just trickier to parse.

However, I have found a common factor across all Xbox games - they all share the same Protocol property of ms-xbl-* which appears to be the Xbox Live protocol, and from my tests it works great (the only issue was that Microsoft Edge still remained for some reason, so I filtered out default MS apps). Thus, I was able to cut all the random edge cases out and instead use this.

For naming, I look at the VisualElements tag and its DisplayName attribute rather than the one in Properties since it was consistently correct, but it will fall back on that if it runs into any issues. I also did my best not to break UTF-8 encoded names like Tetris!

Before (running python xbox.py with the test limited to print just the name):

Scanning Xbox library...
Collected 12 games from the Xbox library   <-- Only lists 12 games when there are 13 (Gears of war 4 missing)
[('Dishonored: Death of the Outsider (PC)',),
 ('Forza Horizon 5',),
 ('FrigateMS',),   <--Incorrectly lists Octopath Traveler as its dev(?) name `FrigateMS`
 ('Halo Infinite',),
 ('LUMINES REMASTERED',),
 ('MechWarrior 5: Mercenaries',),
 ('Tetris® Effect: Connected',),
 ('The Ascent',),
 ('The Forgotten City',),
 ('The Medium',),
 ('Visage',),
 ('Wreckfest',)]

After:

Scanning Xbox library...
Collected 13 games from the Xbox library   <-- Properly detects Gears 4 now
[('Dishonored: Death of the Outsider (PC)',),
 ('Forza Horizon 5',),
 ('Gears of War 4',),   <-- Now detected!
 ('Halo Infinite',),
 ('LUMINES REMASTERED',),
 ('MechWarrior 5: Mercenaries',),
 ('OCTOPATH TRAVELER',),   <-- Properly named!
 ('Tetris® Effect: Connected',),
 ('The Ascent',),
 ('The Forgotten City',),
 ('The Medium',),
 ('Visage',),
 ('Wreckfest',)]
jaydenmilne commented 2 years ago

Hi, sorry I've been busy with the holidays. @idbrii you interested in doing code review here?

idbrii commented 2 years ago

Thanks for the PR! It improves naming for one of my installed games.

Unfortunately, this PR no longer detects two of my games.

Before:

Scanning Xbox library...
Collected 16 games from the Xbox library
[('Curse of the Dead Gods',),
 ('Genesis Noir for Windows',), # no longer detected after PR
 ('Ghost of a Tale PC',),
 ('Gorogoa',),
 ('Katamari Damacy Reroll_Windows',), # PR correctly excludes _Windows
 ('Maneater',),
 ('Monster Train',),
 ('Olija Win10',),
 ('PAW Patrol Mighty Pups Save Adventure Bay',),
 ('Prey',),
 ('Raji An Ancient Epic',),
 ('ReCore',), # no longer detected after PR
 ('Ring of Pain',),
 ('Supraland',),
 ('Tetris® Effect: Connected',),
 ('The Pedestrian',)]

After:

Scanning Xbox library...
Collected 14 games from the Xbox library
[('Curse of the Dead Gods',),
 ('Ghost of a Tale PC',),
 ('Gorogoa',),
 ('Katamari Damacy Reroll',),
 ('Maneater',),
 ('Monster Train',),
 ('Olija Win10',),
 ('PAW Patrol Mighty Pups Save Adventure Bay',),
 ('Prey',),
 ('Raji An Ancient Epic',),
 ('Ring of Pain',),
 ('Supraland',),
 ('Tetris® Effect: Connected',),
 ('The Pedestrian',)]

27c72cf74457a1d2a8bf051dc54c9a0d8ab94b0e did some special work for Genesis Noir, but I don't recall whether it's very relevant.

Here's GenesisNoir's appxmanifest.xml (with some "Resource" lines removed):

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:wincap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/windowscapabilities" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" IgnorableNamespaces="uap uap3 desktop desktop6 wincap rescap" xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10">
  <Identity Name="SurpriseAttackPtyLtd.GenesisNoirforWindows" Publisher="CN=D40ABC2D-5045-4676-AF66-568F0C42A3BB" Version="1.0.24.0" ProcessorArchitecture="x64" />
  <Properties>
    <DisplayName>Genesis Noir for Windows</DisplayName>
    <PublisherDisplayName>Surprise Attack Pty Ltd</PublisherDisplayName>
    <Logo>StoreLogo.png</Logo>
    <Description>The game that was made</Description>
  </Properties>
  <Dependencies>
    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
    <PackageDependency Name="Microsoft.VCLibs.140.00.UWPDesktop" MinVersion="14.0.24217.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
    <PackageDependency Name="Microsoft.DirectXRuntime" MinVersion="9.29.952.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
  </Dependencies>
  <Resources>
    <Resource Language="en" />

  </Resources>
  <Applications>
    <Application Id="GenesisNoir" Executable="GenesisNoir.exe" EntryPoint="Windows.FullTrustApplication">
      <uap:VisualElements DisplayName="Genesis Noir for Windows" Square150x150Logo="Logo.png" Square44x44Logo="SmallLogo.png" Description="The game that was made" ForegroundText="light" BackgroundColor="#000040">
        <uap:SplashScreen Image="SplashScreen.png" />
      </uap:VisualElements>
    </Application>
  </Applications>
  <Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="runFullTrust" />
  </Capabilities>
</Package>

And this is what's in the applist for it:

 {'Appid': 'SurpriseAttackPtyLtd.GenesisNoirforWindows',
  'Aumid': 'SurpriseAttackPtyLtd.GenesisNoirforWindows_8k24hnfn3vvj0!GenesisNoir',
  'Icon': 'C:\\Program '
          'Files\\WindowsApps\\SurpriseAttackPtyLtd.GenesisNoirforWindows_1.0.24.0_x64__8k24hnfn3vvj0\\SmallLogo.png',
  'InstallLocation': 'C:\\Program '
                     'Files\\WindowsApps\\SurpriseAttackPtyLtd.GenesisNoirforWindows_1.0.24.0_x64__8k24hnfn3vvj0',
  'Kind': 'GenesisNoir',
  'PrettyName': 'Genesis Noir for Windows'}

And ReCore:

<?xml version="1.0" encoding="utf-8"?>
<Package xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" IgnorableNamespaces="uap mp build" xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:build="http://schemas.microsoft.com/developer/appx/2015/build">

  <Identity Name="Microsoft.ReCore" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="1.1.7468.2" ProcessorArchitecture="x64" />
  <mp:PhoneIdentity PhoneProductId="31e96dfc-2610-4197-8110-d070382611d5" PhonePublisherId="c0566bf3-b08a-4984-9de3-63aea1f3eba6" />
  <Properties>
    <DisplayName>ReCore</DisplayName>
    <PublisherDisplayName>Microsoft Studios</PublisherDisplayName>
    <Logo>Assets\StoreLogo.jpg</Logo>
  </Properties>
  <Dependencies>
    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.98" MaxVersionTested="10.0.14393.98" />
    <PackageDependency Name="Microsoft.VCLibs.140.00" MinVersion="14.0.24123.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
    <PackageDependency Name="Microsoft.NET.Native.Framework.1.3" MinVersion="1.3.24201.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
    <PackageDependency Name="Microsoft.NET.Native.Runtime.1.4" MinVersion="1.4.24201.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
  </Dependencies>
  <Resources>
    <Resource Language="EN-US" />

    <Resource uap:DXFeatureLevel="dx11" />
  </Resources>
  <Applications>
    <Application Id="App" Executable="ReCore.exe" EntryPoint="Microsoft.ReCore.App">
      <uap:VisualElements DisplayName="ReCore" Square150x150Logo="Assets\Square150x150Logo.jpg" Square44x44Logo="Assets\Square44x44Logo.jpg" Description="ReCore" BackgroundColor="#000000">
        <uap:DefaultTile ShortName="ReCore" Wide310x150Logo="Assets\Wide310x150Logo.jpg" Square71x71Logo="Assets\Square71x71Logo.jpg" Square310x310Logo="Assets\Square310x310Logo.jpg" />
        <uap:SplashScreen Image="Assets\SplashScreen.jpg" BackgroundColor="#FFFFFF" />
        <uap:InitialRotationPreference>
          <uap:Rotation Preference="landscape" />
          <uap:Rotation Preference="landscapeFlipped" />
          <uap:Rotation Preference="portrait" />
          <uap:Rotation Preference="portraitFlipped" />
        </uap:InitialRotationPreference>
      </uap:VisualElements>
    </Application>
  </Applications>
  <Capabilities>
    <Capability Name="internetClient" />
    <Capability Name="internetClientServer" />
    <Capability Name="codeGeneration" />
  </Capabilities>
  <Extensions>
    <Extension Category="windows.activatableClass.inProcessServer">
      <InProcessServer>
        <Path>ReCore.dll</Path>
      </InProcessServer>
    </Extension>
    <Extension Category="windows.activatableClass.inProcessServer">
      <InProcessServer>
        <Path>Microsoft.Xbox.Services.dll</Path>
      </InProcessServer>
    </Extension>
    <Extension Category="windows.activatableClass.inProcessServer">
      <InProcessServer>
        <Path>UnityEngineDelegates.dll</Path>
      </InProcessServer>
    </Extension>
    <Extension Category="windows.activatableClass.inProcessServer">
      <InProcessServer>
        <Path>UnityPlayer.dll</Path>
      </InProcessServer>
    </Extension>
  </Extensions>
  <build:Metadata>
    <build:Item Name="TargetFrameworkMoniker" Value=".NETCore,Version=v5.0" />
    <build:Item Name="VisualStudio" Version="14.0" />
    <build:Item Name="OperatingSystem" Version="10.0.15063.0 (WinBuild.160101.0800)" />
    <build:Item Name="Microsoft.Build.AppxPackage.dll" Version="14.0.25431.1" />
    <build:Item Name="ProjectGUID" Value="{13a45c13-3265-47b8-bc48-a553b569df55}" />
    <build:Item Name="ilc.exe" Version="1.4.24208.01 built by: PROJECTNGDR2" />
    <build:Item Name="OptimizingToolset" Value="ilc.exe" />
    <build:Item Name="UseDotNetNativeSharedAssemblyFrameworkPackage" Value="True" />
    <build:Item Name="UniversalGenericsOptOut" Value="false" />
    <build:Item Name="Microsoft.Windows.UI.Xaml.Build.Tasks.dll" Version="14.0.25527.1" />
    <build:Item Name="XboxLive" Version="1.0" />
    <build:Item Name="MakePri.exe" Version="10.0.14393.795 (rs1_release_sec.170105-1850)" />
  </build:Metadata>
</Package>
   {'Appid': 'Microsoft.ReCore',
    'Aumid': 'Microsoft.ReCore_8wekyb3d8bbwe!App',
    'Icon': 'C:\\Program '
            'Files\\WindowsApps\\Microsoft.ReCore_1.1.7468.2_x64__8wekyb3d8bbwe\\Assets\\Square44x44Logo.jpg',
    'InstallLocation': 'C:\\Program '
                       'Files\\WindowsApps\\Microsoft.ReCore_1.1.7468.2_x64__8wekyb3d8bbwe',
    'Kind': 'App',
    'PrettyName': 'ReCore'}

Neither have a protocol. My guess is that's because there's two editions of the game. Ex: Genesis Noir Xbox and Genesis Noir Windows. So we probably need to keep some of the old code when there's no Protocol.

Also please squash your blacken into your changes (if you had to blacken existing code, it's fine).