Last active 1618884604

Life Is Strange stats server investigation

docs.md Raw

Contents

In the below documentation, all requests return { "d": something } (except the ones that don't), so everything outside of something will be omitted.

API url: https://lis.os.eidos.com/

Necessary headers for each request (may vary, untested):

  • Accept: application/json, otherwise the API returns XML (obviously if you're fine with that you can omit this)
  • OS-AuthProvider: 6, unsure of the purpose of this
  • OS-AuthTicketData: [string], begins with CAEQ on my copy of the game, CONFIRMED static between copies of the game (at least PC linux)
  • OS-AuthTicketSize: 44, the length of the above string
  • OS-UID: [string], steam user ID, or some other form of identification in standalone versions probably
  • OS-Platform: steam, the user's platform, needs more testing to get options
  • OS-System: linux, ditto

Optional headers:

  • DataServiceVersion: 2.0
  • MaxDataServiceVersion: 2.0
  • OS-Age: 0
  • OS-Build: localBuild
  • OS-GTime: 84
  • OS-Locale: en,XX,XX,
  • OS-MAC: xx-xx-xx-xx-xx-xx
  • OS-OSVersion: 5.0.22.82369, unsure what this is, it's not kernel version
  • OS-PID: LiS-v1.0.0.397609
  • OS-Progress: 0.00000000000000000, lol
  • OS-SID: [uuid]
  • OS-STime: 5713
  • OS-XYZ: 135.95315551757812500,-130.51196289062500000,97.04180908203125000
  • OS-Zone: E0_Menu, the game seems to send ingame location with every request

On game start

The game sends several requests in the opening videos.

  1. /game/os_Ping

    • Likely confirming that the service is up
    • Returns {"os_Ping": 6739}, different number each time
  2. /game/os_GetServiceInfo

  3. /game/os_Ping again

  4. /game/SEM_Login

    • Probably for the Square Enix account login
    • Query params: s_type is 'UID' here; s_value is my steam user ID, a long string of digits
    • Returns { "SEM_Login": { "__metadata": { "type": "game.SEMSubmit" }, "b_confirmed": true, "s_SEMID": "string of numbers", "s_email": "email address", "s_facebookID": "probably facebook user identifier", "s_longTermToken": "string of numbers and letters" } }
  5. It sends the above request twice, unclear why

  6. /game/CreateUserProfile

    • Square Enix login again? Probably making sure that there's an account for the given steam ID for stats collection even if it isn't linked to a SEM account
    • Query params: s_uid is my steam user ID
    • Returns { "CreateUserProfile": true } and a header OS-AuthResponse containing the new OS-AuthTicketData to be used for subsequent requests
  7. /game/GetTodaysInfocast

    • Returns a list of messages to be scrolled at the bottom of the main menu
    • Idling on the menu calls this periodically
    • Query params: b_digital, a boolean, probably whether it's digital or a physical disc; i16_episode, the episode number of the current save game; s_locale, language code e.g 'en'
    • Example response: https://gist.github.com/blha303/d00b1c34728cc21101b7fc198bbf3371
  8. /game/SetUserProfileFriends

    • Sends list of UIDs for user's friends list
    • POST requests, ["UID", ...]
    • Returns {"SetUserProfileFriends": 0 }, friends service probably disabled
  9. /game/GetSeasonPassOfferIdList

Occasionally

  1. /game/AddMetrics

On opening the choices screen (from the main menu or after each episode)

  1. /game/CommunityFactsGetEpisode

  2. /game/GetFriendsProfileStats

    • Apparently would return the same as /game/CommunityFactsGetEpisode with additional info on friend stats, but currently returns nothing
    • Query params: i16_episode, the given episode number; s_uid, the steam/other user id
    • Currently returns {"results": [] }

On finishing an episode

Making me listen to Obstacles again >_<

  1. /game/UpdateUserProfileGameSpecific

Other endpoints: https://gist.github.com/blha303/101e0db0bf63ea07b1f55862947c9065#file-zgenerateddocs-md

revlis.md Raw

Contents

Hello! Today I thought I'd take on the task of reverse engineering the stats screen in Life Is Strange so I could easily request and display the data without needing to start the game. I'm documenting it here for future use, since nobody else seems to have tried this yet.

First, you'll need a copy of Life Is Strange on Steam. Second, you'll need to have it working on Linux. The Linux Steam version of LIS has a startup script that sets the ssl certificate locations, you'll need to modify that to be able to MITM the server connections. The game makes requests to https://lis.os.eidos.com, but doesn't verify the certificate in the game. (requests to Feral Interactive, the company that ported LIS to OSX and Linux, do seem to be verified, but they don't matter much)

    #HAS_CURL=$( command -v curl-config )
    #if [ -n "$...
    #    SSL_CERT_FILE=...
    #else
    #    if ...
    #        SSL_CERT...
    #    elif
    #        SSL_CERT...
    #    elif
    #        SSL_CERT...
    #    fi
    #fi
    export SSL_CERT_FILE="/home/user/Downloads/mitmproxy-ca-cert.pem"
  • Set the second computer as the default gateway on the first computer by running sudo ip route add default via <ip>. Get the ip address of the second computer by running ip addr | grep inet.
  • Close other programs (except Terminal and Steam) to minimize noise
  • Run mitmdump -w output.mitmdump in Terminal, then start Life Is Strange through Steam
  • I had to alt-tab out and into the game to get it to work, you may need to do the same
  • You should see requests to /game/os_Ping start showing up. mitmdump will record all game communication. I loaded up the main menu, clicked on Choices, waited for the stats to show up, then closed the game and killed mitmdump with ctrl-c
  • Run mitmproxy --host -r output.mitmdump to view the requests.

If only the stats server communications weren't encrypted, would have been a lot easier.

I'm not sure how initial authentication works, the game sends an OS-AuthTicketData header with a string of characters, maybe each copy of the game has a unique auth ticket confirmed to be static at least for PC Linux releases. It also requests user location data via an IP info lookup (/game/os_GetServiceInfo) and sends your friends list in the form of a list of user IDs (/game/SetUserProfileFriends), presumably for the friend stats response (/game/GetFriendsProfileStats) but this currently returns nothing. The main thing I'm interested in is /game/CommunityFactsGetEpisode, which takes a query param of i16_episode={episode number} and returns json:

"d" : {
    "results" : [
        {
            "__metadata" : {
                "uri" : "https://lis.os.eidos.com/game/communityfactreturns('BirdDead')",
                "type" : "game.communityfactreturn"
            },
            "f_rate" : "0.638969873663751",
            "i64_totalCount" : 2058,
            "i64_trueCount" : 1315,
            "i16_ep" : 1,
            "s_factID" : "BirdDead"
        },
        {
            "__metadata" : {
                "uri" : "https://lis.os.eidos.com/game/communityfactreturns('BirdSaved')",
                "type" : "game.communityfactreturn"
            },
            "f_rate" : "0.361030126336249",
            "i64_totalCount" : 2058,
            "i64_trueCount" : 743,
            "i16_ep" : 1,
            "s_factID" : "BirdSaved"
        }, ...

Which is exactly what I was looking for ^_^

zGenerateDocs.py Raw
1#!/usr/bin/env python3
2from json import load
3
4with open("/tmp/lisserviceinfo.json") as f: # from os_GetServiceInfo
5 d = load(f)["d"]
6
7f = d["Metadata"]["Schemas"][0]["EntityContainers"][0]["FunctionImports"]
8
9funcs = {}
10for func in f:
11 if not func["AuthPolicy"] in funcs:
12 funcs[func["AuthPolicy"]] = []
13 funcs[func["AuthPolicy"]].append(func)
14
15def format(func):
16 out = "* `/game/{Name}`\n".format(**func)
17 if "Parameters" in func:
18 func["params"] = ["`{Name}`, {Type}".format(**p) for p in func["Parameters"]]
19 out += " * Query params: " + "; ".join(func["params"]) + "\n"
20 if func["HttpMethod"] != "GET":
21 out += " * {HttpMethod} request\n".format(**func)
22 if "ReturnType" in func:
23 out += " * Returns {ReturnType}".format(**func)
24 else:
25 out += " * Returns nothing"
26 return out
27
28print("These docs aren't entirely accurate. Some things are in the wrong AuthPolicy category, some things say they return nothing when they do return something. But then, since when are any internal docs accurate, right? :P\n")
29for authpolicy, functions in funcs.items():
30 print("\n### {}".format(authpolicy))
31 for f in functions:
32 try:
33 print(format(f))
34 except:
35 print(f)
36 raise
37
zGeneratedDocs.md Raw

Contents

These docs aren't entirely accurate. Some things are in the wrong AuthPolicy category, some things say they return nothing when they do return something. But then, since when are any internal docs accurate, right? :P

Policy.OS

  • /game/CalculateCommunityProfileStats
    • Returns Edm.Int32
  • /game/CalculateFriendsProfileStats
    • Returns Edm.Int32
  • /game/UpdateAvailableEpisodeOfferId
    • Query params: s_region, Edm.String; i32_episode, Edm.Int32
    • Returns Edm.String
  • /game/os_GetMonitoring
    • Returns game.ServerStatus
  • /game/os_GetOptions
    • Returns game.Options
  • /game/os_SetOptions
    • Query params: name, Edm.String; value, Edm.String
    • Returns game.Options
  • /game/ClearCache
    • Query params: s_Name, Edm.String
    • Returns Edm.String
  • /game/os_NotificationExpired
    • Query params: s_id, Edm.String; i32_type, Edm.Int32; s_message, Edm.String; s_platform, Edm.String; s_sender, Edm.String
    • Returns nothing
  • /game/DeleteUserProfile
    • Query params: s_uid, Edm.String
    • Returns Edm.Boolean
  • /game/GetUserProfileGlobal
    • Query params: s_uid, Edm.String
    • Returns game.userprofile
  • /game/os_GetUserProfileGlobal
    • Query params: s_uid, Edm.String
    • Returns game.userprofile
  • /game/UpdateUserProfileGlobal
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/os_UpdateUserProfileGlobal
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/GetUserProfilePlatform
    • Query params: s_uid, Edm.String
    • Returns game.userprofile
  • /game/os_GetUserProfilePlatform
    • Query params: s_uid, Edm.String
    • Returns game.userprofile
  • /game/UpdateUserProfilePlatform
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/os_UpdateUserProfilePlatform
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/GetUserProfileFranchise
    • Query params: s_uid, Edm.String
    • Returns game.userprofile
  • /game/os_GetUserProfileFranchise
    • Query params: s_uid, Edm.String
    • Returns game.userprofile
  • /game/UpdateUserProfileFranchise
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/os_UpdateUserProfileFranchise
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/GetUserProfileGameGeneric
    • Query params: s_uid, Edm.String
    • Returns game.userprofile
  • /game/os_GetUserProfileGameGeneric
    • Query params: s_uid, Edm.String
    • Returns game.userprofile
  • /game/UpdateUserProfileGameGeneric
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/os_UpdateUserProfileGameGeneric
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/ValidateSEM
    • Returns Edm.Boolean
  • /game/os_BackupUserProfiles
    • Returns Edm.Int32
  • /game/os_TestCache
    • Query params: s_cacheName, Edm.String
    • Returns Edm.String

Policy.GAMESEE

  • /game/GetSpecialFlag
    • Query params: i32_episode, Edm.Int32
    • Returns game.offlinespecialfeature
  • /game/os_SendNotification
    • Query params: s_sender, Edm.String; i32_type, Edm.Int32; s_platform, Edm.String; i64_lifespan, Edm.Int64
    • POST request
    • Returns nothing
  • /game/os_TranslatePlatformIds
    • POST request
    • Returns Collection(game.PlatformIDInfo)
  • /game/osGetConsumablesInfo
    • Query params: s_uid, Edm.String
    • Returns Edm.String
  • /game/Steam_GetUserInfo
    • Query params: s_uid, Edm.String
    • Returns game.ossteamresponse
  • /game/os_GetLastMetrics
    • Query params: i32_n, Edm.Int32
    • Returns Collection(game.os_metric)
  • /game/os_GetMetric
    • Query params: s_uuid, Edm.String
    • Returns game.os_metric
  • /game/GetUserProfileGameSpecific
    • Query params: s_uid, Edm.String
    • Returns game.userprofile
  • /game/os_GetUserProfileGameSpecific
    • Query params: s_uid, Edm.String
    • Returns game.userprofile
  • /game/GetUserProfileCountry
    • Query params: s_uid, Edm.String
    • Returns Edm.String
  • /game/os_GetUserProfileCountry
    • Query params: s_uid, Edm.String
    • Returns Edm.String
  • /game/UpdateSEMinfo
    • Query params: s_semid, Edm.String
    • POST request
    • Returns Edm.Boolean
  • /game/DeleteSEMinfo
    • Query params: s_semid, Edm.String
    • Returns Edm.Boolean
  • /game/LinkToSEM
    • Query params: s_semid, Edm.String; s_authSystem, Edm.String; s_authId, Edm.String
    • Returns Edm.Boolean
  • /game/UnlinkFromSEM
    • Query params: s_semid, Edm.String; s_authSystem, Edm.String; s_authId, Edm.String
    • Returns Edm.Boolean

Policy.SEE

  • /game/UpdateInfocastMessages
    • Query params: _id, Edm.String; s_Type, Edm.String; s_DateBegin, Edm.String; s_DateEnd, Edm.String; i16_episode, Edm.Int32
    • POST request
    • Returns Edm.Boolean
  • /game/DeleteInfocastMessages
    • Query params: _id, Edm.String
    • Returns Edm.Boolean
  • /game/GetInfocastMessages
    • Returns Collection(game.infocast)
  • /game/EnableSpecialFlag
    • Query params: i32_episode, Edm.Int32; b_enable, Edm.Boolean
    • POST request
    • Returns Edm.Boolean
  • /game/AddInfocastMessages
    • Query params: s_Type, Edm.String; s_DateBegin, Edm.String; s_DateEnd, Edm.String; i16_episode, Edm.Int32
    • POST request
    • Returns Edm.String
  • /game/os_CreateSegment
    • Query params: s_segment, Edm.String
    • POST request
    • Returns Edm.String
  • /game/os_GetSegmentMetadata
    • Query params: s_segment, Edm.String
    • Returns Edm.String
  • /game/os_GetSegmentList
    • Returns Edm.String
  • /game/os_UpdateSegmentMetadata
    • Query params: s_segment, Edm.String
    • POST request
    • Returns Edm.String
  • /game/os_DeleteSegment
    • Query params: s_segment, Edm.String
    • Returns Edm.String
  • /game/os_AddPlayersToSegment
    • Query params: s_segment, Edm.String
    • POST request
    • Returns Edm.String
  • /game/os_GetPlayersFromSegment
    • Query params: s_segment, Edm.String
    • Returns Edm.String
  • /game/os_GetSegmentsFromPlayer
    • Query params: s_uid, Edm.String
    • Returns Edm.String
  • /game/os_RemovePlayersFromSegment
    • Query params: s_segment, Edm.String
    • POST request
    • Returns Edm.String
  • /game/os_AddUsageLog
    • Query params: s_segment, Edm.String; s_log, Edm.String
    • Returns Edm.String
  • /game/os_GetUsageLogs
    • Query params: s_segment, Edm.String
    • Returns Edm.String
  • /game/os_CreateABTest
    • Query params: s_abtest, Edm.String
    • POST request
    • Returns Edm.String
  • /game/os_GetABTestMetadata
    • Query params: s_abtest, Edm.String
    • Returns Edm.String
  • /game/os_GetABTestList
    • Returns Edm.String
  • /game/os_UpdateABTestMetadata
    • Query params: s_abtest, Edm.String
    • POST request
    • Returns Edm.String
  • /game/os_DeleteABTest
    • Query params: s_abtest, Edm.String
    • Returns Edm.String
  • /game/os_AddUsageLogToABTest
    • Query params: s_abtest, Edm.String; s_log, Edm.String
    • Returns Edm.String
  • /game/os_GetUsageLogsFromABTest
    • Query params: s_abtest, Edm.String
    • Returns Edm.String
  • /game/os_AddSegmentToABTest
    • Query params: s_abtest, Edm.String; s_segment, Edm.String
    • Returns Edm.String
  • /game/os_RemoveSegmentFromABTest
    • Query params: s_abtest, Edm.String; s_segment, Edm.String
    • Returns Edm.String
  • /game/os_SetDefaultABTestSegment
    • Query params: s_abtest, Edm.String; s_segment, Edm.String
    • Returns Edm.String
  • /game/os_SetDefaultABTestSegmentAsRandom
    • Query params: s_abtest, Edm.String
    • Returns Edm.String
  • /game/os_GetPlayerSegmentFromABTest
    • Query params: s_abtest, Edm.String; s_uid, Edm.String
    • Returns Edm.String
  • /game/os_CheckABTestConsistency
    • Query params: s_abtest, Edm.String
    • Returns Edm.String

Policy.ANY

  • /game/AddMetric
    • Returns nothing
  • /game/AddMetrics
    • POST request
    • Returns nothing

Policy.PUBLIC

  • /game/GetTodaysInfocast
    • Query params: s_locale, Edm.String; i16_episode, Edm.Int32
    • Returns Collection(game.infocast)
  • /game/os_GetStatus
    • Returns game.ClientInfo
  • /game/os_GetServiceInfo
    • Returns nothing
  • /game/os_GetChangeLog
    • Returns nothing
  • /game/os_GetChangeLogHTML
    • Returns nothing
  • /game/os_Ping
    • Returns Edm.Int64
  • /game/SEM_Login
    • Query params: s_type, Edm.String; s_value, Edm.String
    • Returns game.SEMSubmit
  • /game/SEM_SubmitEmail
    • Query params: s_type, Edm.String; s_value, Edm.String
    • Returns game.SEMSubmit

Policy.GAME

  • /game/UpdateUserProfileGameSpecific
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/SetUserProfileFriends
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/CommunityFactsGetEpisode
    • Query params: i16_episode, Edm.Int32
    • Returns Collection(game.communityfactreturn)
  • /game/CommunityFactsGetAll
    • Returns Collection(game.communityfactreturn)
  • /game/GetFriendsProfileStats
    • Query params: s_uid, Edm.String; i16_episode, Edm.Int32
    • Returns Collection(game.communityfactreturn)
  • /game/GetSeasonPassOfferIdList
    • Query params: s_region, Edm.String
    • Returns Collection(game.offerid)
  • /game/SetUserProfileGameSpecific
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/os_GetPlayer
    • Query params: UID, Edm.String
    • Returns game.os_player
  • /game/os_GetGeoLocation
    • Returns game.GeoLocation
  • /game/SEM_DeleteUser
    • Query params: s_type, Edm.String; s_value, Edm.String
    • Returns game.Result
  • /game/SEM_UnlinkPlayer
    • Query params: s_type, Edm.String; s_value, Edm.String
    • Returns game.Result
  • /game/SEM_GetLinkedAccounts
    • Query params: s_type, Edm.String; s_value, Edm.String
    • Returns Collection(game.SEMLinkedAccount)
  • /game/FB_PostToWall
    • Query params: s_facebookID, Edm.String
    • POST request
    • Returns game.Result
  • /game/FB_PostImageToWall
    • Query params: s_facebookID, Edm.String; s_imageName, Edm.String; s_linkName, Edm.String; s_linkCaption, Edm.String; s_linkDescription, Edm.String
    • POST request
    • Returns game.Result
  • /game/osNewTransaction
    • Query params: s_uid, Edm.String; s_offerIds, Edm.String
    • Returns game.txninfo
  • /game/osCreateCustomTransaction
    • Query params: s_uid, Edm.String
    • POST request
    • Returns game.txninfo
  • /game/osFinalizeTransaction
    • Query params: s_uid, Edm.String; i64_txnId, Edm.Int64
    • Returns game.txninfo
  • /game/osCancelTransaction
    • Query params: s_uid, Edm.String; i64_txnId, Edm.Int64; i32_reason, Edm.Int32
    • Returns game.txninfo
  • /game/osPreTransferTransaction
    • Query params: s_uid, Edm.String; i64_txnId, Edm.Int64
    • POST request
    • Returns game.txninfo
  • /game/osGetIncompleteTransaction
    • Query params: s_uid, Edm.String
    • Returns Collection(game.txninfo)
  • /game/CreateUserProfile
    • Query params: s_uid, Edm.String
    • Returns Edm.Boolean
  • /game/os_CreateUserProfile
    • Query params: s_uid, Edm.String
    • Returns Edm.Int32
  • /game/os_SetUserProfileGameSpecific
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/os_UpdateUserProfileGameSpecific
    • Query params: s_uid, Edm.String
    • POST request
    • Returns Edm.Int32
  • /game/SetUserProfileCountry
    • Query params: s_uid, Edm.String; s_country, Edm.String
    • Returns Edm.Boolean
  • /game/os_SetUserProfileCountry
    • Query params: s_uid, Edm.String; s_country, Edm.String
    • Returns Edm.Boolean
  • /game/PS4_GetFriendList
    • Query params: s_uid, Edm.String
    • Returns Edm.String