diff --git a/src/internal/deobfuscateAppleGPU.ts b/src/internal/deobfuscateAppleGPU.ts index 9c06c3f..c3e3442 100644 --- a/src/internal/deobfuscateAppleGPU.ts +++ b/src/internal/deobfuscateAppleGPU.ts @@ -13,6 +13,7 @@ import { // Internal import { deviceInfo } from './deviceInfo'; +import { getAppleGPUFromCapabilities } from './getAppleGPUFromCapabilities'; const debug = false ? console.warn : undefined; @@ -21,22 +22,41 @@ export function deobfuscateAppleGPU( renderer: string, isMobileTier: boolean ) { - if (!isMobileTier) { - debug?.('Safari 14+ obfuscates its GPU type and version, using fallback'); - return [renderer]; - } const pixelId = calculateMagicPixelId(gl); const codeA = '801621810' as const; const codeB = '8016218135' as const; const codeC = '80162181161' as const; const codeFB = '80162181255'; + const codeM = '80162181255' as const; // Observed on Apple Silicon Macs + + // Desktop Apple Silicon chipsets + const desktopChipsets: [ + string, + typeof codeA | typeof codeB | typeof codeC | typeof codeM, + number, + ][] = [ + ['m1', codeM, 11], // Released with macOS 11 Big Sur + ['m1 pro', codeM, 12], // Released with macOS 12 Monterey + ['m1 max', codeM, 12], // Released with macOS 12 Monterey + ['m1 ultra', codeM, 12], // Released with macOS 12 Monterey + ['m2', codeM, 12], // Released with macOS 12 Monterey + ['m2 pro', codeM, 13], // Released with macOS 13 Ventura + ['m2 max', codeM, 13], // Released with macOS 13 Ventura + ['m2 ultra', codeM, 13], // Released with macOS 13 Ventura + ['m3', codeM, 14], // Released with macOS 14 Sonoma + ['m3 pro', codeM, 14], // Released with macOS 14 Sonoma + ['m3 max', codeM, 14], // Released with macOS 14 Sonoma + ['m4', codeM, 14], // First on iPad, then Mac with macOS 14 + ['m4 pro', codeM, 15], // Released with macOS 15 Sequoia + ['m4 max', codeM, 15], // Released with macOS 15 Sequoia + ]; // All chipsets that support at least iOS 12: const possibleChipsets: [ string, - typeof codeA | typeof codeB | typeof codeC, + typeof codeA | typeof codeB | typeof codeC | typeof codeM, number, - ][] = deviceInfo?.isIpad + ][] = !isMobileTier ? desktopChipsets : deviceInfo?.isIpad ? [ // ['a4', 5], // ipad 1st gen // ['a5', 9], // ipad 2 / ipad mini 1st gen @@ -86,9 +106,21 @@ export function deobfuscateAppleGPU( chipsets = possibleChipsets; } } - const renderers = chipsets.map(([gpu]) => `apple ${gpu} gpu`); + + // For desktop, if we only have generic matches, use capability-based detection + if (!isMobileTier && chipsets.length === desktopChipsets.length) { + const capabilityBasedGPUs = getAppleGPUFromCapabilities(gl); + debug?.( + `Using capability-based detection for desktop Safari, possible GPUs: ${JSON.stringify( + capabilityBasedGPUs + )}` + ); + return capabilityBasedGPUs; + } + + const renderers = chipsets.map(([gpu]) => !isMobileTier ? `apple ${gpu}` : `apple ${gpu} gpu`); debug?.( - `iOS 12.2+ obfuscates its GPU type and version, using closest matches: ${JSON.stringify( + `${isMobileTier ? 'iOS 12.2+' : 'Safari 14+'} obfuscates its GPU type and version, using closest matches: ${JSON.stringify( renderers )}` ); diff --git a/src/internal/getAppleGPUFromCapabilities.ts b/src/internal/getAppleGPUFromCapabilities.ts new file mode 100644 index 0000000..bc5da2e --- /dev/null +++ b/src/internal/getAppleGPUFromCapabilities.ts @@ -0,0 +1,61 @@ +export function getAppleGPUFromCapabilities( + gl: WebGLRenderingContext | WebGL2RenderingContext +): string[] { + // Get various WebGL capabilities that differ between Apple Silicon generations + const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); + const maxVertexUniformVectors = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS); + const maxFragmentUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS); + const maxVaryingVectors = gl.getParameter(gl.MAX_VARYING_VECTORS); + const maxVertexTextureImageUnits = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS); + const maxCombinedTextureImageUnits = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); + const maxRenderbufferSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE); + + // WebGL2 specific capabilities + let maxDrawBuffers = 4; + let maxColorAttachments = 4; + let max3DTextureSize = 0; + let maxArrayTextureLayers = 0; + + if ((gl as WebGL2RenderingContext).MAX_DRAW_BUFFERS) { + const gl2 = gl as WebGL2RenderingContext; + maxDrawBuffers = gl2.getParameter(gl2.MAX_DRAW_BUFFERS); + maxColorAttachments = gl2.getParameter(gl2.MAX_COLOR_ATTACHMENTS); + max3DTextureSize = gl2.getParameter(gl2.MAX_3D_TEXTURE_SIZE); + maxArrayTextureLayers = gl2.getParameter(gl2.MAX_ARRAY_TEXTURE_LAYERS); + } + + // Calculate a capability score + const score = + (maxTextureSize / 4096) + + (maxVertexUniformVectors / 256) + + (maxFragmentUniformVectors / 256) + + (maxVaryingVectors / 16) + + (maxVertexTextureImageUnits / 16) + + (maxCombinedTextureImageUnits / 32) + + (maxRenderbufferSize / 8192) + + (maxDrawBuffers / 4) + + (maxColorAttachments / 4) + + (max3DTextureSize / 2048) + + (maxArrayTextureLayers / 2048); + + // Estimate GPU based on capability score + // These thresholds are approximate and may need tuning + if (score >= 15) { + // High-end M-series (M4 Pro/Max, M3 Pro/Max, M2 Ultra, M1 Ultra) + return ['apple m4 pro', 'apple m4 max', 'apple m3 pro', 'apple m3 max', 'apple m2 ultra', 'apple m1 ultra']; + } else if (score >= 12) { + // Mid-range M-series (M4, M3, M2 Pro, M1 Pro/Max) + return ['apple m4', 'apple m3', 'apple m2 pro', 'apple m1 pro', 'apple m1 max']; + } else if (score >= 10) { + // Base M-series (M2, M1) + return ['apple m2', 'apple m1']; + } else { + // Fallback to all desktop Apple GPUs + return [ + 'apple m4 pro', 'apple m4', + 'apple m3 pro', 'apple m3', + 'apple m2 pro', 'apple m2', + 'apple m1 pro', 'apple m1' + ]; + } +} \ No newline at end of file diff --git a/test-browser.html b/test-browser.html new file mode 100644 index 0000000..7946e88 --- /dev/null +++ b/test-browser.html @@ -0,0 +1,164 @@ + + +
+ + +