Skip to content

Commit

Permalink
[Fix] Token gated games install/update (#1195)
Browse files Browse the repository at this point in the history
* revert later: add debug logs

* fix token gated games download with non mm ext and update

* rm debug logs

* use .text(), improve error handling

* rm " from nonce response
  • Loading branch information
BrettCleary authored Dec 19, 2024
1 parent 16b4176 commit f9f1fe6
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 55 deletions.
2 changes: 2 additions & 0 deletions src/backend/api/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,5 @@ export const installSteamWindows = async () =>

export const importGameFolder = async (gameFolder: string) =>
ipcRenderer.invoke('importGameFolder', gameFolder)

export const requestSIWE = async () => ipcRenderer.invoke('requestSIWE')
2 changes: 2 additions & 0 deletions src/backend/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,8 @@ ipcMain.handle(
}
)

ipcMain.handle('requestSIWE', HyperPlayGameManager.requestSIWE)

ipcMain.handle('getEpicGamesStatus', async () => isEpicServiceOffline())

ipcMain.handle('getMaxCpus', () => cpus().length)
Expand Down
68 changes: 66 additions & 2 deletions src/backend/storeManagers/hyperplay/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ import { getFlag } from 'backend/flags/flags'
import { ipfsGateway } from 'backend/vite_constants'
import { GlobalConfig } from 'backend/config'
import { PatchingError } from './types'
import { SiweMessage } from 'siwe'

interface ProgressDownloadingItem {
DownloadItem: DownloadItem
Expand Down Expand Up @@ -1397,6 +1398,51 @@ export async function launch(appName: string): Promise<boolean> {
return launchGame(appName, getGameInfo(appName), 'hyperplay')
}

async function createSiweMessage(signerAddress: string): Promise<SiweMessage> {
const mainWindowUrl = getMainWindow()?.webContents.getURL()
if (mainWindowUrl === undefined) {
throw 'could not get main window url'
}
const url = new URL(mainWindowUrl)
const domain = url.host ? url.host : 'hyperplay'
const origin = url.origin.startsWith('file://')
? 'file://hyperplay'
: url.origin

const statementRes = await fetch(
DEV_PORTAL_URL + 'api/v1/license_contracts/validate/get-nonce'
)
if (!statementRes.ok) {
const responseError = await statementRes.text()
throw new Error(`Failed to get nonce for SIWE message. ${responseError}`)
}
const nonce = await statementRes.text()

return new SiweMessage({
domain,
address: signerAddress,
statement: nonce.replaceAll('"', ''),
uri: origin,
version: '1',
chainId: 1
})
}

export async function requestSIWE() {
const providers = await import('@hyperplay/providers')
const signer = await providers.provider.getSigner()
const address = await signer.getAddress()
const siweMessage = await createSiweMessage(address)
const message = siweMessage.prepareMessage()
const signature = await signer.signMessage(message)

return {
message,
signature,
address
}
}

// TODO: Refactor to only replace updated files
export async function update(
appName: string,
Expand All @@ -1423,8 +1469,6 @@ export async function update(
return { status: 'error' }
}

const isMarketWars = gameInfo.account_name === 'marketwars'

const {
channels,
install: { channelName, platform, install_size, install_path, executable }
Expand All @@ -1446,6 +1490,24 @@ export async function update(
throw new Error('Channel name or platform not found')
}

const channelRequiresToken = !!channels[channelName]?.license_config.tokens

if (channelRequiresToken && args?.siweValues === undefined) {
// request from frontend
if (args === undefined) {
args = {}
}
try {
args.siweValues = await requestSIWE()
} catch (err) {
logError(
`Could not get SIWE sig for updating token gated game. ${err}`,
LogPrefix.HyperPlay
)
captureException(err)
}
}

const newVersion = channels[channelName].release_meta.name
const abortController = createAbortController(appName)
const { status, error } = await applyPatching(
Expand All @@ -1454,6 +1516,8 @@ export async function update(
abortController.signal
)

const isMarketWars = gameInfo.account_name === 'marketwars'

if (status === 'abort') {
logWarning(`Patching ${appName} aborted`, LogPrefix.HyperPlay)
return { status: 'abort' }
Expand Down
5 changes: 5 additions & 0 deletions src/common/typedefs/ipcBridge.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,11 @@ interface AsyncIPCFunctions extends HyperPlayAsyncIPCFunctions {
getQuestsForGame: (projectId: string) => Promise<Quest[]>
installSteamWindows: () => Promise<void>
isClientUpdating: () => Promise<ClientUpdateStatuses>
requestSIWE: () => Promise<{
message: string
signature: string
address: string
}>
}

// This is quite ugly & throws a lot of errors in a regular .ts file
Expand Down
50 changes: 1 addition & 49 deletions src/frontend/helpers/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@ import {
import { TFunction } from 'i18next'
import { getGameInfo, getPlatformName } from './index'
import { DialogModalOptions } from 'frontend/types'
import { SiweMessage } from 'siwe'
import { ethers } from 'ethers'
import axios from 'axios'
import authState from 'frontend/state/authState'
import gameUpdateState from 'frontend/state/GameUpdateState'
import { DEV_PORTAL_URL } from 'common/constants'

const storage: Storage = window.localStorage

Expand Down Expand Up @@ -279,7 +275,7 @@ const updateGame = async (gameInfo: GameInfo) => {
.tokens
let siweValues = undefined
if (channelRequiresTokens) {
siweValues = await signSiweMessage()
siweValues = await window.api.requestSIWE()
}
return gameUpdateState.updateGame({ ...gameInfo, siweValues })
}
Expand All @@ -290,47 +286,3 @@ export const sideloadedCategories = ['all', 'sideload']
export const hyperPlayCategories = ['all', 'hyperplay']

export { install, launch, repair, updateGame }

export async function signSiweMessage(): Promise<SiweValues> {
const signer = await getSigner()
const address = await signer.getAddress()

const siweMessage = await createSiweMessage(address)
const message = siweMessage.prepareMessage()
const signature = await signer.signMessage(message)

return {
message,
signature,
address
}
}

export async function getSigner(): Promise<ethers.Signer> {
if (!window.ethereum) throw new Error('Ethereum provider not found')
const provider = new ethers.BrowserProvider(window.ethereum)
return provider.getSigner()
}

export async function createSiweMessage(
signerAddress: string
): Promise<SiweMessage> {
const domain = window.location.host ? window.location.host : 'hyperplay'
const origin = window.location.origin.startsWith('file://')
? 'file://hyperplay'
: window.location.origin

const statementRes = await axios.get(
DEV_PORTAL_URL + 'api/v1/license_contracts/validate/get-nonce'
)
const statement = String(statementRes?.data)

return new SiweMessage({
domain,
address: signerAddress,
statement,
uri: origin,
version: '1',
chainId: 1
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import { configStore } from 'frontend/helpers/electronStores'
import { AlertCard, Button, Images } from '@hyperplay/ui'
import DLCDownloadListing from './DLCDownloadListing'
import { useEstimatedUncompressedSize } from 'frontend/hooks/useEstimatedUncompressedSize'
import { signSiweMessage } from 'frontend/helpers/library'
import styles from './index.module.scss'

interface Props {
Expand Down Expand Up @@ -206,7 +205,7 @@ export default function DownloadDialog({
async function handleInstall(path?: string) {
let siweValues
if (requiresToken) {
siweValues = await signSiweMessage()
siweValues = await window.api.requestSIWE()
}
backdropClick()
// Write Default game config with prefix on linux
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { configStore } from 'frontend/helpers/electronStores'
import TextInputWithIconField from 'frontend/components/UI/TextInputWithIconField'
import { Button, Images } from '@hyperplay/ui'
import { install } from 'frontend/helpers'
import { signSiweMessage } from 'frontend/helpers/library'
import ContextProvider from 'frontend/state/ContextProvider'
import { useFlags } from 'launchdarkly-react-client-sdk'
import { marketWarsLinksFallback } from './constants'
Expand Down Expand Up @@ -94,7 +93,7 @@ const ModDialog: React.FC<Props> = ({
let siweValues

if (requiresToken) {
siweValues = await signSiweMessage()
siweValues = await window.api.requestSIWE()
}

// Write Default game config with prefix on linux
Expand Down

0 comments on commit f9f1fe6

Please sign in to comment.