Skip to content

Commit 17c3831

Browse files
flavioislimajiyuu-jingithub-advanced-security[bot]BrettCleary
authored
[FEAT] Client IPDT Patching implementation (#1087)
* feat: add patcher pkg and downloader * fix: vite config * feat: check and download patcher on startup * chore: update valist sdk * wip: download updated manifests * chore: use dashes instead of underscore to avoid issues when parsing strings * tech: use game api to download manifest * chore: comments, imports, variable * feat: add applyPatch method * feat: add patching to update method * feat: add getIpdtVersion method * Add patching section to .gitignore * Bump hp/patcher to 0.0.8 * Refactor downloadLatestGameIpdtManifest to downloadGameIpdtManifest and add optional version param * lint * lint * Run translations * Bump hp/patcher to 0.0.9 * Add getHyperPlayReleaseManifest calls to applyPatching function * Bump hp/patcher to 0.0.10 * tech: download manifest from getManifest if not found * fix: lint * Update @hyperplay/patcher to 0.0.12-alpha.4 * Update @hyperplay/patcher to 0.0.12-alpha.5 * Fix code scanning alert no. 183: Incomplete string escaping or encoding Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Bump @hyperplay/patcher version * Bump @hyperplay/patcher version * tech: update patcher pkg * tech: refactor applyPatching * tech: downgrade patcher and get API from env * Bump @hyperplay/patcher version * WIP: patching progress on frontend * ui: download progress * fix: downspeed on download screen * fix: lint * ui: calculate actuall download size on update * Bump @hyperplay/patcher version * Bump @hyperplay/patcher version * fix: totalSize * chore: update patcher version * fix: update library.json after patching * fix: refresh library after patch * Return IPDT Data Downloaded in bytes * Add terminate callback to patchFolder * tech: add abort handler and update size calculation * fix: return proper abort status on cancel * fix: progress and ETA * fix: download size from dm * chore: logs * feat: use maxWorkers setting on patching method * chore: stop flooding logs with appName empty * chore: add more abort checks to fix windows edge cases * ui: add patching status * ui: if patching show only cancel * ui: fix game page status message and progress * feat: add LD flag * fix: ui messages * i18n: updated keys * chore: yarn.lock * tech: add analytics and error capture for patching events * chore: tsconfig removed setting * ci: updated release builds with ipfs secret * fix: duplicated runs * chore: revert tsconfig change * fix: tests? * tests: try another fix * chore: remove maxWorkers option * chore: add kubo-rpc-client dependency * chore: revert jest and package.json file * chore: update ui-lib * fix: pr comments and better error handling * tech: use fs-promises to deal with manifests and remove unused code * fix: don't show pause button on gamecard * fix: don't show pause button on download queue * fix: downloaded data on dm * tests: third try? * tech: use dynamic import for patcher lib * chore: add patcher to tsignore * tech: move launchdarkly initialization to its own file * tech: get LD init to main again * tech: add LD helpers * Bump @hyperplay/patcher version * tech: use constants for import.meta for easy mocking * tess: updated mocks * fix: lint * chore: pr comments * fix: race condition when downloading manifest + add more logs * tech: always download ipdt bin * fix: import * move valist sdk to dep (#1138) * tech: updated ui lib * Bump hyperplay/patcher version for datastoreDir * Fix build * Make pretty * tech: proper deal with fetch result Co-authored-by: Brett <27568879+BrettCleary@users.noreply.github.com> * tech: download manifest only on game update * tech: add temp dir and deal with connection refused * tech: update patcher and add workers param * Bump @hyperplay/patcher version * tech: update patcher version to add upt support * fix: download size on dm after finish * fix: should update current element as well * chore: removed log * chore: add elapsed time on hovering finished time * fix: pr comments * tech: add platform to launchdarkly context * fix: time elapsed for current * chore: patch on windows only * fix: lint * fix: version * chore: code cleanup --------- Co-authored-by: Flavio F Lima <flavioislima@users.noreply.github.com> Co-authored-by: jiyuu-jin <zach@valist.io> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Brett <27568879+BrettCleary@users.noreply.github.com>
1 parent 9235a6c commit 17c3831

File tree

53 files changed

+1011
-125
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1011
-125
lines changed

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
VITE_LD_ENVIRONMENT_ID=<LAUNCHDARKLY_ENVIRONMENT_ID>
2+
VITE_IPFS_API=<IPFS_API_URL>

.github/workflows/build-mac-arm64.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ jobs:
3434
run: yarn setup
3535

3636
- name: setup env production file
37-
run: echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID_TEST }}" > .env
37+
run: |
38+
echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID_TEST }}" > .env
39+
echo "VITE_IPFS_API=${{ secrets.VITE_IPFS_API }}" >> .env
3840
3941
- name: Build artifacts.
4042
run: yarn release:mac:arm64

.github/workflows/build.yml

+12-6
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ jobs:
3535
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
3636
- name: Install modules.
3737
run: yarn install --network-timeout 600000 && yarn allow-scripts
38-
- name: setup env production file
39-
run: echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID_TEST }}" > .env
38+
- name: setup env file
39+
run: |
40+
echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID_TEST }}" > .env
41+
echo "VITE_IPFS_API=${{ secrets.VITE_IPFS_API }}" >> .env
4042
- name: Build artifacts.
4143
run: yarn dist:win
4244
- name: Upload EXE.
@@ -71,8 +73,10 @@ jobs:
7173
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
7274
- name: Install modules.
7375
run: yarn setup
74-
- name: setup env production file
75-
run: echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID_TEST }}" > .env
76+
- name: setup env file
77+
run: |
78+
echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID_TEST }}" > .env
79+
echo "VITE_IPFS_API=${{ secrets.VITE_IPFS_API }}" >> .env
7680
- name: Build deb artifact
7781
run: yarn dist:linux:ci:deb
7882
- name: Build rpm artifact
@@ -101,8 +105,10 @@ jobs:
101105
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
102106
- name: Install modules.
103107
run: yarn setup
104-
- name: setup env production file
105-
run: echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID_TEST }}" > .env
108+
- name: setup env file
109+
run: |
110+
echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID_TEST }}" > .env
111+
echo "VITE_IPFS_API=${{ secrets.VITE_IPFS_API }}" >> .env
106112
- name: Build artifacts.
107113
run: yarn dist:mac:x64
108114
env:

.github/workflows/flatpak-build.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ jobs:
4444
- name: install yarn
4545
run: npm install --global yarn
4646
- name: setup env production file
47-
run: echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID_TEST }}" > .env
47+
run: |
48+
echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID }}" > .env.production
49+
echo "VITE_IPFS_API=${{ secrets.VITE_IPFS_API }}" >> .env.production
4850
- name: Install modules.
4951
run: yarn setup
5052
- name: Build artifacts.

.github/workflows/release_linux.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ jobs:
3434
- name: Install modules.
3535
run: yarn setup
3636
- name: setup env production file
37-
run: echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID }}" > .env.production
37+
run: |
38+
echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID }}" > .env.production
39+
echo "VITE_IPFS_API=${{ secrets.VITE_IPFS_API }}" >> .env.production
3840
- name: Build artifacts.
3941
run: yarn run release:linux

.github/workflows/release_macOS.yml

+6-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ jobs:
4242
run: yarn setup
4343

4444
- name: setup env production file
45-
run: echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID }}" > .env.production
45+
run: |
46+
echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID }}" > .env.production
47+
echo "VITE_IPFS_API=${{ secrets.VITE_IPFS_API }}" >> .env.production
4648
4749
- name: Build artifacts.
4850
run: yarn release:mac:x64
@@ -84,7 +86,9 @@ jobs:
8486
run: yarn setup
8587

8688
- name: setup env production file
87-
run: echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID }}" > .env.production
89+
run: |
90+
echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID }}" > .env.production
91+
echo "VITE_IPFS_API=${{ secrets.VITE_IPFS_API }}" >> .env.production
8892
8993
- name: Build artifacts.
9094
run: yarn release:mac:arm64

.github/workflows/release_snap.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ jobs:
3131
- name: Install modules.
3232
run: yarn setup
3333
- name: setup env production file
34-
run: echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID }}" > .env.production
34+
run: |
35+
echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID }}" > .env.production
36+
echo "VITE_IPFS_API=${{ secrets.VITE_IPFS_API }}" >> .env.production
3537
- name: Update package on Snap Store
3638
run: yarn run dist:linux snap --publish always

.github/workflows/release_windows.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ jobs:
4949
run: yarn install --frozen-lockfile --network-timeout 600000 && yarn allow-scripts
5050

5151
- name: setup env production file
52-
run: echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID }}" > .env.production
52+
run: |
53+
echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID }}" > .env.production
54+
echo "VITE_IPFS_API=${{ secrets.VITE_IPFS_API }}" >> .env.production
5355
5456
- name: Cache artifacts
5557
uses: actions/cache@v2

.github/workflows/test.yml

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ jobs:
2828
- name: Authenticate with private NPM package
2929
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
3030

31+
- name: setup env file
32+
run: |
33+
echo "VITE_LD_ENVIRONMENT_ID=${{ secrets.VITE_LD_ENVIRONMENT_ID_TEST }}" > .env
34+
echo "VITE_IPFS_API=${{ secrets.VITE_IPFS_API }}" >> .env
35+
3136
- name: Install modules.
3237
run: yarn setup
3338

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ xyz.hyperplay.HyperPlay
5252
/public/extensions/resetExecutables/resetExtension-macos
5353
/public/extensions/resetExecutables/resetExtension.exe
5454
/public/extensions/resetExecutables/resetExtension
55+
electron.vite.config..*
5556

5657
electron.vite.config.*.mjs
5758

5859
.MMSDK_cached_chainId
5960
.MMSDK_cached_address
61+
62+
# Patching
63+
blockstore

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@
170170
"@hyperplay/chains": "^0.3.0",
171171
"@hyperplay/check-disk-space": "^3.5.2",
172172
"@hyperplay/quests-ui": "^0.0.28",
173-
"@hyperplay/ui": "^1.8.8",
173+
"@hyperplay/ui": "^1.8.9",
174174
"@hyperplay/utils": "^0.3.3",
175175
"@mantine/carousel": "^7.12.0",
176176
"@mantine/core": "^7.12.0",
@@ -185,6 +185,7 @@
185185
"@shockpkg/icon-encoder": "^2.1.3",
186186
"@tanstack/query-core": "^5.59.0",
187187
"@tanstack/react-query": "^5.51.23",
188+
"@valist/sdk": "^2.10.5",
188189
"@walletconnect/browser-utils": "^1.8.0",
189190
"@walletconnect/ethereum-provider": "^2.10.6",
190191
"@walletconnect/modal": "^2.6.0",
@@ -334,7 +335,6 @@
334335
"@types/ws": "^8.5.12",
335336
"@typescript-eslint/eslint-plugin": "^6.21.0",
336337
"@typescript-eslint/parser": "^6.21.0",
337-
"@valist/sdk": "^2.9.17",
338338
"@vitejs/plugin-react": "^4.3.1",
339339
"cross-env": "^7.0.3",
340340
"electron": "^32.0.1",
@@ -366,6 +366,7 @@
366366
"@hyperplay/extension-importer": "^0.0.4",
367367
"@hyperplay/extension-provider": "^0.0.8",
368368
"@hyperplay/mock-backend": "^0.0.1",
369+
"@hyperplay/patcher": "^0.0.17",
369370
"@hyperplay/overlay": "^0.0.7",
370371
"@hyperplay/providers": "^0.0.6",
371372
"@hyperplay/proxy-server": "^0.0.11"

public/locales/en/gamepage.json

+1
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@
202202
"notinstalled": "This game is not installed",
203203
"notSupported": "Not supported",
204204
"notSupportedGame": "Not Supported",
205+
"patching": "Patching Files ",
205206
"paused": "Paused",
206207
"playing": "Playing",
207208
"preparing": "Preparing Download, please wait",

public/locales/en/translation.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@
366366
"extracting": "Extracting...",
367367
"installed": "Ready to play",
368368
"installing": "Downloading...",
369+
"patching": "Patching...",
369370
"paused": "Paused"
370371
},
371372
"linkSteamAccountCta": "Go to Steam sign in",
@@ -807,7 +808,8 @@
807808
},
808809
"pause": "Pause download",
809810
"remove": "Remove from Downloads",
810-
"resume": "Resume download"
811+
"resume": "Resume download",
812+
"timeElapsed": "Time Elapsed: {{elapsed}}"
811813
}
812814
},
813815
"Recent": "Played Recently",

src/backend/__tests__/constants.test.ts

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { fixAsarPath } from '../constants'
22

33
jest.mock('../logger/logfile')
4+
jest.mock('backend/vite_constants', () => ({
5+
VITE_IPFS_API: 'https://ipfs.io/ipfs/'
6+
}))
7+
jest.mock('backend/flags/flags', () => ({
8+
VITE_LD_ENVIRONMENT_ID: '123'
9+
}))
410

511
describe('Constants - fixAsarPath', () => {
612
test('need to fix path and replace correctly', () => {

src/backend/__tests__/main_window.test.ts

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import { BrowserWindow, Display, screen } from 'electron'
33
import { configStore } from '../constants'
44

55
jest.mock('../logger/logfile')
6+
jest.mock('backend/vite_constants', () => ({
7+
VITE_IPFS_API: 'https://ipfs.io/ipfs/'
8+
}))
9+
jest.mock('backend/flags/flags', () => ({
10+
VITE_LD_ENVIRONMENT_ID: '123'
11+
}))
612

713
describe('main_window', () => {
814
describe('sendFrontendMessage', () => {

src/backend/__tests__/utils.test.ts

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ jest.mock('electron')
55
jest.mock('../logger/logger')
66
jest.mock('../logger/logfile')
77
jest.mock('../dialog/dialog')
8+
jest.mock('backend/vite_constants', () => ({
9+
VITE_IPFS_API: 'https://ipfs.io/ipfs/'
10+
}))
11+
jest.mock('backend/flags/flags', () => ({
12+
VITE_LD_ENVIRONMENT_ID: '123'
13+
}))
814

915
describe('backend/utils.ts', () => {
1016
test('quoteIfNeccessary', () => {

src/backend/api/downloadmanager.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,14 @@ export const updateGame = (gameInfo: GameInfo, accessCode?: string) => {
4949
path: install_path!,
5050
platformToInstall: platform!,
5151
accessCode,
52-
siweValues
52+
siweValues,
53+
channelName: gameInfo.install.channelName
5354
},
5455
type: 'update',
5556
addToQueueTime: Date.now(),
5657
endTime: 0,
57-
startTime: 0
58+
startTime: 0,
59+
channel: gameInfo.install.channelName
5860
}
5961

6062
ipcRenderer.invoke('addToDMQueue', dmQueueElement)

src/backend/constants.ts

+28-10
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ const cachedUbisoftInstallerPath = join(
7272
'UbisoftConnectInstaller.exe'
7373
)
7474

75+
const ipdtPatcher = join(toolsPath, 'ipdt')
76+
const ipdtManifestsPath = join(appConfigFolder, 'manifests')
77+
7578
const { currentLogFile, lastLogFile, legendaryLogFile, gogdlLogFile } =
7679
createNewLogFileAndClearOldOnes()
7780

@@ -119,6 +122,8 @@ export function getValistListingApiUrl(projectId: string) {
119122
)
120123
}
121124

125+
export const patchApiUrl = `${DEV_PORTAL_URL}api/v1/patches`
126+
122127
export function getValidateLicenseKeysApiUrl() {
123128
return `${DEV_PORTAL_URL}api/v1/license_keys/validate`
124129
}
@@ -202,20 +207,31 @@ const execOptions = {
202207
shell: getShell()
203208
}
204209

205-
const defaultFolders = [gamesConfigPath, iconsFolder, imagesCachePath]
210+
const defaultFolders = [
211+
gamesConfigPath,
212+
iconsFolder,
213+
imagesCachePath,
214+
toolsPath,
215+
ipdtManifestsPath
216+
]
206217

207-
const necessaryFoldersByPlatform = {
218+
const necessaryFoldersByPlatform: {
219+
[key in 'win32' | 'linux' | 'darwin']: string[]
220+
} = {
208221
win32: [...defaultFolders],
209-
linux: [...defaultFolders, toolsPath],
210-
darwin: [...defaultFolders, toolsPath]
222+
linux: [...defaultFolders],
223+
darwin: [...defaultFolders]
211224
}
212225

213226
export function createNecessaryFolders() {
214-
necessaryFoldersByPlatform[platform()].forEach((folder: string) => {
215-
if (!existsSync(folder)) {
216-
mkdirSync(folder)
217-
}
218-
})
227+
const platformKey = platform() as 'win32' | 'linux' | 'darwin'
228+
if (necessaryFoldersByPlatform[platformKey]) {
229+
necessaryFoldersByPlatform[platformKey].forEach((folder: string) => {
230+
if (!existsSync(folder)) {
231+
mkdirSync(folder)
232+
}
233+
})
234+
}
219235
}
220236

221237
const onboardLocalStore = new TypeCheckedStoreBackend('onboardingStore', {
@@ -282,5 +298,7 @@ export {
282298
valistListingsApiUrl,
283299
mainReleaseChannelName,
284300
vulkanHelperBin,
285-
cachedUbisoftInstallerPath
301+
cachedUbisoftInstallerPath,
302+
ipdtManifestsPath,
303+
ipdtPatcher
286304
}

0 commit comments

Comments
 (0)