Contents
- Docs
- Setting up capture process
- Automatically generated API documentation (inaccurate)
- Script to generate API documentation
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 thisOS-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 stringOS-UID: [string], steam user ID, or some other form of identification in standalone versions probablyOS-Platform: steam, the user's platform, needs more testing to get optionsOS-System: linux, ditto
Optional headers:
DataServiceVersion: 2.0MaxDataServiceVersion: 2.0OS-Age: 0OS-Build: localBuildOS-GTime: 84OS-Locale: en,XX,XX,OS-MAC: xx-xx-xx-xx-xx-xxOS-OSVersion: 5.0.22.82369, unsure what this is, it's not kernel versionOS-PID: LiS-v1.0.0.397609OS-Progress: 0.00000000000000000, lolOS-SID: [uuid]OS-STime: 5713OS-XYZ: 135.95315551757812500,-130.51196289062500000,97.04180908203125000OS-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.
-
/game/os_Ping- Likely confirming that the service is up
- Returns
{"os_Ping": 6739}, different number each time
-
/game/os_GetServiceInfo- Returns information about the IP address used for connection and a list of metadata and endpoints.
- Example output: https://gist.github.com/blha303/8c5b925f95c23c08197ac3a82e1bee15
-
/game/os_Pingagain -
/game/SEM_Login- Probably for the Square Enix account login
- Query params:
s_typeis 'UID' here;s_valueis 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" } }
-
It sends the above request twice, unclear why
-
/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_uidis my steam user ID - Returns
{ "CreateUserProfile": true }and a headerOS-AuthResponsecontaining the newOS-AuthTicketDatato be used for subsequent requests
-
/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
-
/game/SetUserProfileFriends- Sends list of UIDs for user's friends list
- POST requests,
["UID", ...] - Returns
{"SetUserProfileFriends": 0 }, friends service probably disabled
-
/game/GetSeasonPassOfferIdList- No clue what this is for, you'd think it would show information on the episodes but it just returns the same thing five times.
- Query params:
s_region, mine says 'na', not applicable? - Returns https://gist.github.com/blha303/cba709bc7b9db5e8122c46133fdedbcb
Occasionally
/game/AddMetrics- Is called every few minutes regardless of the client configuration returned by
os_GetServiceInfo - POST request, https://gist.github.com/blha303/b79ba499a2ea61880d3fc8dc812f7eb4
- Returns nothing at all
- Is called every few minutes regardless of the client configuration returned by
On opening the choices screen (from the main menu or after each episode)
-
/game/CommunityFactsGetEpisode- Returns a list of choices for a given episode, and the percentage and raw numbers for each choice
- Query params:
i16_episode, the given episode number - Example response for episode 1: https://gist.github.com/blha303/cec90d1d2e351c33d39ddddd880cd252
-
/game/GetFriendsProfileStats- Apparently would return the same as
/game/CommunityFactsGetEpisodewith 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": [] }
- Apparently would return the same as
On finishing an episode
Making me listen to Obstacles again >_<
/game/UpdateUserProfileGameSpecific- POST request, summarizes choices made. Called when credits start
- JSON data, https://gist.github.com/blha303/144499eeb67dface9a83371c17f9513b
- Returns
0, maybe means profile isn't updated when the given UID has already completed the given chapter
Other endpoints: https://gist.github.com/blha303/101e0db0bf63ea07b1f55862947c9065#file-zgenerateddocs-md
Contents
- Docs
- Setting up capture process
- Automatically generated API documentation (inaccurate)
- Script to generate API documentation
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)
- Set up mitmproxy on a laptop or other second computer that won't be running the game: http://docs.mitmproxy.org/en/stable/install.html
- Set up transparent proxying http://docs.mitmproxy.org/en/stable/transparent/linux.html
- Edit
.steam/steam/steamapps/common/Life Is Strange/LifeIsStrange.shon the first machine, and edit the lines belowHAS_CURLlike so:
#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 runningip addr | grep inet. - Close other programs (except Terminal and Steam) to minimize noise
- Run
mitmdump -w output.mitmdumpin 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.mitmdumpto 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 ^_^
| 1 | #!/usr/bin/env python3 |
| 2 | from json import load |
| 3 | |
| 4 | with open("/tmp/lisserviceinfo.json") as f: # from os_GetServiceInfo |
| 5 | d = load(f)["d"] |
| 6 | |
| 7 | f = d["Metadata"]["Schemas"][0]["EntityContainers"][0]["FunctionImports"] |
| 8 | |
| 9 | funcs = {} |
| 10 | for func in f: |
| 11 | if not func["AuthPolicy"] in funcs: |
| 12 | funcs[func["AuthPolicy"]] = [] |
| 13 | funcs[func["AuthPolicy"]].append(func) |
| 14 | |
| 15 | def 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 | |
| 28 | print("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") |
| 29 | for 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 |
Contents
- Docs
- Setting up capture process
- Automatically generated API documentation (inaccurate)
- Script to generate API documentation
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
- Query params:
/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
- Query params:
/game/ClearCache- Query params:
s_Name, Edm.String - Returns Edm.String
- Query params:
/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
- Query params:
/game/DeleteUserProfile- Query params:
s_uid, Edm.String - Returns Edm.Boolean
- Query params:
/game/GetUserProfileGlobal- Query params:
s_uid, Edm.String - Returns game.userprofile
- Query params:
/game/os_GetUserProfileGlobal- Query params:
s_uid, Edm.String - Returns game.userprofile
- Query params:
/game/UpdateUserProfileGlobal- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/os_UpdateUserProfileGlobal- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/GetUserProfilePlatform- Query params:
s_uid, Edm.String - Returns game.userprofile
- Query params:
/game/os_GetUserProfilePlatform- Query params:
s_uid, Edm.String - Returns game.userprofile
- Query params:
/game/UpdateUserProfilePlatform- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/os_UpdateUserProfilePlatform- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/GetUserProfileFranchise- Query params:
s_uid, Edm.String - Returns game.userprofile
- Query params:
/game/os_GetUserProfileFranchise- Query params:
s_uid, Edm.String - Returns game.userprofile
- Query params:
/game/UpdateUserProfileFranchise- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/os_UpdateUserProfileFranchise- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/GetUserProfileGameGeneric- Query params:
s_uid, Edm.String - Returns game.userprofile
- Query params:
/game/os_GetUserProfileGameGeneric- Query params:
s_uid, Edm.String - Returns game.userprofile
- Query params:
/game/UpdateUserProfileGameGeneric- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/os_UpdateUserProfileGameGeneric- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/ValidateSEM- Returns Edm.Boolean
/game/os_BackupUserProfiles- Returns Edm.Int32
/game/os_TestCache- Query params:
s_cacheName, Edm.String - Returns Edm.String
- Query params:
Policy.GAMESEE
/game/GetSpecialFlag- Query params:
i32_episode, Edm.Int32 - Returns game.offlinespecialfeature
- Query params:
/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
- Query params:
/game/os_TranslatePlatformIds- POST request
- Returns Collection(game.PlatformIDInfo)
/game/osGetConsumablesInfo- Query params:
s_uid, Edm.String - Returns Edm.String
- Query params:
/game/Steam_GetUserInfo- Query params:
s_uid, Edm.String - Returns game.ossteamresponse
- Query params:
/game/os_GetLastMetrics- Query params:
i32_n, Edm.Int32 - Returns Collection(game.os_metric)
- Query params:
/game/os_GetMetric- Query params:
s_uuid, Edm.String - Returns game.os_metric
- Query params:
/game/GetUserProfileGameSpecific- Query params:
s_uid, Edm.String - Returns game.userprofile
- Query params:
/game/os_GetUserProfileGameSpecific- Query params:
s_uid, Edm.String - Returns game.userprofile
- Query params:
/game/GetUserProfileCountry- Query params:
s_uid, Edm.String - Returns Edm.String
- Query params:
/game/os_GetUserProfileCountry- Query params:
s_uid, Edm.String - Returns Edm.String
- Query params:
/game/UpdateSEMinfo- Query params:
s_semid, Edm.String - POST request
- Returns Edm.Boolean
- Query params:
/game/DeleteSEMinfo- Query params:
s_semid, Edm.String - Returns Edm.Boolean
- Query params:
/game/LinkToSEM- Query params:
s_semid, Edm.String;s_authSystem, Edm.String;s_authId, Edm.String - Returns Edm.Boolean
- Query params:
/game/UnlinkFromSEM- Query params:
s_semid, Edm.String;s_authSystem, Edm.String;s_authId, Edm.String - Returns Edm.Boolean
- Query params:
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
- Query params:
/game/DeleteInfocastMessages- Query params:
_id, Edm.String - Returns Edm.Boolean
- Query params:
/game/GetInfocastMessages- Returns Collection(game.infocast)
/game/EnableSpecialFlag- Query params:
i32_episode, Edm.Int32;b_enable, Edm.Boolean - POST request
- Returns Edm.Boolean
- Query params:
/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
- Query params:
/game/os_CreateSegment- Query params:
s_segment, Edm.String - POST request
- Returns Edm.String
- Query params:
/game/os_GetSegmentMetadata- Query params:
s_segment, Edm.String - Returns Edm.String
- Query params:
/game/os_GetSegmentList- Returns Edm.String
/game/os_UpdateSegmentMetadata- Query params:
s_segment, Edm.String - POST request
- Returns Edm.String
- Query params:
/game/os_DeleteSegment- Query params:
s_segment, Edm.String - Returns Edm.String
- Query params:
/game/os_AddPlayersToSegment- Query params:
s_segment, Edm.String - POST request
- Returns Edm.String
- Query params:
/game/os_GetPlayersFromSegment- Query params:
s_segment, Edm.String - Returns Edm.String
- Query params:
/game/os_GetSegmentsFromPlayer- Query params:
s_uid, Edm.String - Returns Edm.String
- Query params:
/game/os_RemovePlayersFromSegment- Query params:
s_segment, Edm.String - POST request
- Returns Edm.String
- Query params:
/game/os_AddUsageLog- Query params:
s_segment, Edm.String;s_log, Edm.String - Returns Edm.String
- Query params:
/game/os_GetUsageLogs- Query params:
s_segment, Edm.String - Returns Edm.String
- Query params:
/game/os_CreateABTest- Query params:
s_abtest, Edm.String - POST request
- Returns Edm.String
- Query params:
/game/os_GetABTestMetadata- Query params:
s_abtest, Edm.String - Returns Edm.String
- Query params:
/game/os_GetABTestList- Returns Edm.String
/game/os_UpdateABTestMetadata- Query params:
s_abtest, Edm.String - POST request
- Returns Edm.String
- Query params:
/game/os_DeleteABTest- Query params:
s_abtest, Edm.String - Returns Edm.String
- Query params:
/game/os_AddUsageLogToABTest- Query params:
s_abtest, Edm.String;s_log, Edm.String - Returns Edm.String
- Query params:
/game/os_GetUsageLogsFromABTest- Query params:
s_abtest, Edm.String - Returns Edm.String
- Query params:
/game/os_AddSegmentToABTest- Query params:
s_abtest, Edm.String;s_segment, Edm.String - Returns Edm.String
- Query params:
/game/os_RemoveSegmentFromABTest- Query params:
s_abtest, Edm.String;s_segment, Edm.String - Returns Edm.String
- Query params:
/game/os_SetDefaultABTestSegment- Query params:
s_abtest, Edm.String;s_segment, Edm.String - Returns Edm.String
- Query params:
/game/os_SetDefaultABTestSegmentAsRandom- Query params:
s_abtest, Edm.String - Returns Edm.String
- Query params:
/game/os_GetPlayerSegmentFromABTest- Query params:
s_abtest, Edm.String;s_uid, Edm.String - Returns Edm.String
- Query params:
/game/os_CheckABTestConsistency- Query params:
s_abtest, Edm.String - Returns Edm.String
- Query params:
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)
- Query params:
/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
- Query params:
/game/SEM_SubmitEmail- Query params:
s_type, Edm.String;s_value, Edm.String - Returns game.SEMSubmit
- Query params:
Policy.GAME
/game/UpdateUserProfileGameSpecific- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/SetUserProfileFriends- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/CommunityFactsGetEpisode- Query params:
i16_episode, Edm.Int32 - Returns Collection(game.communityfactreturn)
- Query params:
/game/CommunityFactsGetAll- Returns Collection(game.communityfactreturn)
/game/GetFriendsProfileStats- Query params:
s_uid, Edm.String;i16_episode, Edm.Int32 - Returns Collection(game.communityfactreturn)
- Query params:
/game/GetSeasonPassOfferIdList- Query params:
s_region, Edm.String - Returns Collection(game.offerid)
- Query params:
/game/SetUserProfileGameSpecific- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/os_GetPlayer- Query params:
UID, Edm.String - Returns game.os_player
- Query params:
/game/os_GetGeoLocation- Returns game.GeoLocation
/game/SEM_DeleteUser- Query params:
s_type, Edm.String;s_value, Edm.String - Returns game.Result
- Query params:
/game/SEM_UnlinkPlayer- Query params:
s_type, Edm.String;s_value, Edm.String - Returns game.Result
- Query params:
/game/SEM_GetLinkedAccounts- Query params:
s_type, Edm.String;s_value, Edm.String - Returns Collection(game.SEMLinkedAccount)
- Query params:
/game/FB_PostToWall- Query params:
s_facebookID, Edm.String - POST request
- Returns game.Result
- Query params:
/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
- Query params:
/game/osNewTransaction- Query params:
s_uid, Edm.String;s_offerIds, Edm.String - Returns game.txninfo
- Query params:
/game/osCreateCustomTransaction- Query params:
s_uid, Edm.String - POST request
- Returns game.txninfo
- Query params:
/game/osFinalizeTransaction- Query params:
s_uid, Edm.String;i64_txnId, Edm.Int64 - Returns game.txninfo
- Query params:
/game/osCancelTransaction- Query params:
s_uid, Edm.String;i64_txnId, Edm.Int64;i32_reason, Edm.Int32 - Returns game.txninfo
- Query params:
/game/osPreTransferTransaction- Query params:
s_uid, Edm.String;i64_txnId, Edm.Int64 - POST request
- Returns game.txninfo
- Query params:
/game/osGetIncompleteTransaction- Query params:
s_uid, Edm.String - Returns Collection(game.txninfo)
- Query params:
/game/CreateUserProfile- Query params:
s_uid, Edm.String - Returns Edm.Boolean
- Query params:
/game/os_CreateUserProfile- Query params:
s_uid, Edm.String - Returns Edm.Int32
- Query params:
/game/os_SetUserProfileGameSpecific- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/os_UpdateUserProfileGameSpecific- Query params:
s_uid, Edm.String - POST request
- Returns Edm.Int32
- Query params:
/game/SetUserProfileCountry- Query params:
s_uid, Edm.String;s_country, Edm.String - Returns Edm.Boolean
- Query params:
/game/os_SetUserProfileCountry- Query params:
s_uid, Edm.String;s_country, Edm.String - Returns Edm.Boolean
- Query params:
/game/PS4_GetFriendList- Query params:
s_uid, Edm.String - Returns Edm.String
- Query params: