Skip to content

[Ref] Use DNS text queries to resolve PCI IDs #3528

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/backend/api/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
} from 'common/types'
import { NileRegisterData } from 'common/types/nile'

export const clearCache = (showDialog?: boolean) =>
ipcRenderer.send('clearCache', showDialog)
export const clearCache = (showDialog?: boolean, fromVersionChange?: boolean) =>
ipcRenderer.send('clearCache', showDialog, fromVersionChange)
export const resetHeroic = () => ipcRenderer.send('resetHeroic')

export const openWeblate = () => ipcRenderer.send('openWeblate')
Expand Down
4 changes: 2 additions & 2 deletions src/backend/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,8 +714,8 @@ ipcMain.handle('getCurrentChangelog', async () => {
return getCurrentChangelog()
})

ipcMain.on('clearCache', (event, showDialog?: boolean) => {
clearCache()
ipcMain.on('clearCache', (event, showDialog, fromVersionChange = false) => {
clearCache(undefined, fromVersionChange)
sendFrontendMessage('refreshLibrary')

if (showDialog) {
Expand Down
14 changes: 13 additions & 1 deletion src/backend/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ import EasyDl from 'easydl'
import decompress from '@xhmikosr/decompress'
import decompressTargz from '@xhmikosr/decompress-targz'
import decompressTarxz from '@felipecrs/decompress-tarxz'
import {
deviceNameCache,
vendorNameCache
} from './utils/systeminfo/gpu/pci_ids'

const execAsync = promisify(exec)

Expand Down Expand Up @@ -367,7 +371,10 @@ async function openUrlOrFile(url: string): Promise<string | void> {
return shell.openPath(url)
}

function clearCache(library?: 'gog' | 'legendary' | 'nile') {
function clearCache(
library?: 'gog' | 'legendary' | 'nile',
fromVersionChange = false
) {
wikiGameInfoStore.clear()
if (library === 'gog' || !library) {
GOGapiInfoCache.clear()
Expand All @@ -387,6 +394,11 @@ function clearCache(library?: 'gog' | 'legendary' | 'nile') {
nileInstallStore.clear()
nileLibraryStore.clear()
}

if (!fromVersionChange) {
deviceNameCache.clear()
vendorNameCache.clear()
}
}

function resetHeroic() {
Expand Down
154 changes: 72 additions & 82 deletions src/backend/utils/systeminfo/gpu/pci_ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,112 +2,102 @@
* Contains helper functions to work with the `pci.ids` file
* ({@link https://pci-ids.ucw.cz})
*/
import axios, { AxiosError } from 'axios'
import path from 'path'

import { downloadFile, DAYS } from '../../inet/downloader'
import { toolsPath } from 'backend/constants'
import { resolveTxt } from 'dns'
import { promisify } from 'util'
import CacheStore from 'backend/cache'

import type { PartialGpuInfo } from './index'
import type { GPUInfo } from '../index'

const pciIdsMap: Record<
string,
{
vendorName: string
devices: Record<
string,
{
deviceName: string
subsystems: Record<`${string} ${string}`, string>
}
>
}
> = {}
const resolveTxtAsync = promisify(resolveTxt)

async function getPicIds(): Promise<typeof pciIdsMap | null> {
if (Object.keys(pciIdsMap).length !== 0) return pciIdsMap
type DeviceNameCacheKey =
| `${string}_${string}_${string}_${string}` // DeviceSubID_VendorSubID_DeviceID_VendorID
| `${string}_${string}` // DeviceID_VendorID
export const deviceNameCache = new CacheStore<string, DeviceNameCacheKey>(
'pci_ids_device',
60 * 24 * 7 // 7 days
)
export const vendorNameCache = new CacheStore<string, string>(
'pci_ids_vendor',
60 * 24 * 7
)

const pciIdsFile = await downloadFile('https://pci-ids.ucw.cz/v2.2/pci.ids', {
axiosConfig: {
responseType: 'text'
},
writeToFile: path.join(toolsPath, 'pci.ids'),
maxCache: 30 * DAYS
}).catch((error) => error as AxiosError)
if (axios.isAxiosError(pciIdsFile)) return null
async function pciIdDnsQuery(pre: string): Promise<string | false> {
const records: string[][] = await resolveTxtAsync(
`${pre}.pci.id.ucw.cz`
).catch(() => [])
return records[0]?.[0]?.replace(/^i=/, '') ?? false
}

let currentVendor: string | null = null
let currentDevice: string | null = null
for (const line of pciIdsFile.split('\n')) {
// Skip comments and empty lines
if (line.startsWith('#')) continue
if (line === '') continue
async function lookupDeviceString(
vendorId: string,
deviceId: string,
subvendorId?: string,
subdeviceId?: string
): Promise<string | false> {
async function lookupKey(key: DeviceNameCacheKey): Promise<string | false> {
const cached = deviceNameCache.get(key)
if (cached) return cached

// Case 1: Line describes a new vendor
const vendorMatch = line.match(/^(.{4}) {2}(.*)$/)
const vendorId = vendorMatch?.[1]
const vendorName = vendorMatch?.[2]
if (vendorId && vendorName) {
pciIdsMap[vendorId] = { vendorName, devices: {} }
currentVendor = vendorId
continue
}
const result = await pciIdDnsQuery(key.replaceAll('_', '.'))
if (result) deviceNameCache.set(key, result)
return result
}

// Case 2: Line describes a new device
const deviceMatch = line.match(/^\t(.{4}) {2}(.*)$/)
const deviceId = deviceMatch?.[1]
const deviceName = deviceMatch?.[2]
if (deviceId && deviceName && currentVendor) {
const vendorObj = pciIdsMap[currentVendor]
if (!vendorObj) continue
vendorObj.devices[deviceId] = { deviceName, subsystems: {} }
currentDevice = deviceId
continue
}
if (!subvendorId || !subdeviceId) return lookupKey(`${deviceId}_${vendorId}`)

// Case 3: Line describes a new subsystem
const subsystemMatch = line.match(/\t\t(.{4}) (.{4}) {2}(.*)$/)
if (!subsystemMatch) continue
const [, subvendor, subdevice, subsystemName] = subsystemMatch
if (
subvendor &&
subdevice &&
subsystemName &&
currentVendor &&
currentDevice
) {
const deviceObj = pciIdsMap[currentVendor]?.devices[currentDevice]
if (!deviceObj) continue
deviceObj.subsystems[`${subvendor} ${subdevice}`] = subsystemName
}
}
// If we have a subdevice and subvendor ID, try getting a name with them first
const resultWithSubIDs = await lookupKey(
`${subdeviceId}_${subvendorId}_${deviceId}_${vendorId}`
)
if (resultWithSubIDs) return resultWithSubIDs

return pciIdsMap
// If there's no name for this specific subdevice and subvendor ID, try
// with just the device and vendor ID
const resultWithoutSubIDs = await lookupKey(`${deviceId}_${vendorId}`)
// Update the cache entry with subIDs, to avoid re-requesting them all the
// time
if (resultWithoutSubIDs)
deviceNameCache.set(
`${subdeviceId}_${subvendorId}_${deviceId}_${vendorId}`,
resultWithoutSubIDs
)
return resultWithoutSubIDs
}

async function lookupVendorString(vendorId: string): Promise<string | false> {
const cached = vendorNameCache.get(vendorId)
if (cached) return cached

const result = await pciIdDnsQuery(vendorId.replaceAll('_', '.'))
if (result) vendorNameCache.set(vendorId, result)
return result
}

async function populateDeviceAndVendorName(
partialGpus: PartialGpuInfo[]
): Promise<GPUInfo[]> {
const pciIds = await getPicIds()
if (pciIds === null) return partialGpus

const fullGpuInfo: GPUInfo[] = []
for (const gpu of partialGpus) {
const vendorId = gpu.vendorId.toLowerCase()
const deviceId = gpu.deviceId.toLowerCase()
const subvendorId = gpu.subvendorId?.toLowerCase()
const subdeviceId = gpu.subdeviceId?.toLowerCase()
const vendor = pciIds[vendorId]
const device = pciIds[vendorId]?.devices[deviceId]
const subsystem =
pciIds[vendorId]?.devices[deviceId]?.subsystems[
`${subvendorId} ${subdeviceId}`
]

const deviceString = await lookupDeviceString(
vendorId,
deviceId,
subvendorId,
subdeviceId
)
const vendorString = await lookupVendorString(vendorId)
if (!deviceString || !vendorString) continue

fullGpuInfo.push({
...gpu,
deviceString: subsystem ?? device?.deviceName,
vendorString: vendor?.vendorName
deviceString,
vendorString
})
}
return fullGpuInfo
Expand Down
2 changes: 1 addition & 1 deletion src/common/typedefs/ipcBridge.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ interface SyncIPCFunctions {
openCustomThemesWiki: () => void
showConfigFileInFolder: (appName: string) => void
removeFolder: ([path, folderName]: [string, string]) => void
clearCache: (showDialog?: boolean) => void
clearCache: (showDialog?: boolean, fromVersionChange?: boolean) => void
resetHeroic: () => void
createNewWindow: (url: string) => void
logoutGOG: () => void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default React.memo(function HeroicVersion() {
window.api.getHeroicVersion().then((version) => {
if (version !== lastVersion) {
window.api.logInfo('Updated to a new version, cleaaning up the cache.')
window.api.clearCache(false)
window.api.clearCache(false, true)
}
storage.setItem('last_version', JSON.stringify(version))
setHeroicVersion(version)
Expand Down