import fs from 'node:fs/promises' const SUMMARY_REF = 'b8170fbc07725bf4930d189ad5dc16f70e09b9cd' const ATLAS_REF = 'a73f0316d9cea52a53381664328bda00e5fe79e4' const EXPECTED_VERSION = '26.3-snapshot-6' const OUTPUT_ROOT = new URL( '../../apps/app-frontend/src/lab/schematic-preview/assets/vanilla/', import.meta.url, ) function summaryUrl(path) { return `https://raw.githubusercontent.com/misode/mcmeta/${SUMMARY_REF}/${path}` } function atlasUrl(path) { return `https://raw.githubusercontent.com/misode/mcmeta/${ATLAS_REF}/${path}` } async function download(url) { let failure for (let attempt = 1; attempt <= 3; attempt++) { try { const response = await fetch(url, { signal: AbortSignal.timeout(120_000) }) if (!response.ok) throw new Error(`HTTP ${response.status}`) return new Uint8Array(await response.arrayBuffer()) } catch (error) { failure = error if (attempt < 3) await new Promise((resolve) => setTimeout(resolve, attempt * 500)) } } throw new Error(`Unable to download ${url}`, { cause: failure }) } function parseJson(bytes, source) { try { return JSON.parse(new TextDecoder().decode(bytes)) } catch (error) { throw new Error(`Invalid JSON from ${source}`, { cause: error }) } } function requireObject(value, source) { if (!value || typeof value !== 'object' || Array.isArray(value)) { throw new Error(`${source} must contain a JSON object`) } return value } function pngDimensions(bytes) { const signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] if (bytes.length < 24 || signature.some((value, index) => bytes[index] !== value)) { throw new Error('The downloaded texture atlas is not a PNG image') } const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) return [view.getUint32(16), view.getUint32(20)] } const sources = { definitions: summaryUrl('assets/block_definition/data.min.json'), models: summaryUrl('assets/model/data.min.json'), blocks: summaryUrl('blocks/data.min.json'), summaryVersion: summaryUrl('version.json'), atlasLayout: atlasUrl('all/data.min.json'), atlasImage: atlasUrl('all/atlas.png'), atlasVersion: atlasUrl('version.json'), } const entries = await Promise.all( Object.entries(sources).map(async ([name, url]) => [name, await download(url)]), ) const downloaded = Object.fromEntries(entries) const summaryVersion = requireObject( parseJson(downloaded.summaryVersion, sources.summaryVersion), 'summary version', ) const atlasVersion = requireObject( parseJson(downloaded.atlasVersion, sources.atlasVersion), 'atlas version', ) if (summaryVersion.id !== EXPECTED_VERSION || atlasVersion.id !== EXPECTED_VERSION) { throw new Error( `Expected ${EXPECTED_VERSION}, received summary ${summaryVersion.id} and atlas ${atlasVersion.id}`, ) } const definitions = requireObject( parseJson(downloaded.definitions, sources.definitions), 'block definitions', ) const models = requireObject(parseJson(downloaded.models, sources.models), 'block models') const blocks = requireObject(parseJson(downloaded.blocks, sources.blocks), 'block summary') const atlasLayout = requireObject( parseJson(downloaded.atlasLayout, sources.atlasLayout), 'atlas layout', ) const defaultProperties = Object.fromEntries( Object.entries(blocks).map(([blockId, summary]) => { if ( !Array.isArray(summary) || !summary[1] || typeof summary[1] !== 'object' || Array.isArray(summary[1]) ) { throw new Error(`Block summary ${blockId} has no default property object`) } return [blockId, summary[1]] }), ) const [atlasWidth, atlasHeight] = pngDimensions(downloaded.atlasImage) for (const [textureId, region] of Object.entries(atlasLayout)) { if ( !Array.isArray(region) || region.length !== 4 || region.some((value) => !Number.isInteger(value) || value < 0) || region[0] + region[2] > atlasWidth || region[1] + region[3] > atlasHeight ) { throw new Error(`Atlas region ${textureId} is outside the ${atlasWidth}x${atlasHeight} image`) } } await Promise.all([ fs.writeFile(new URL('block-state-index.json', OUTPUT_ROOT), JSON.stringify(definitions)), fs.writeFile(new URL('block-model-index.json', OUTPUT_ROOT), JSON.stringify(models)), fs.writeFile( new URL('block-property-defaults.json', OUTPUT_ROOT), JSON.stringify(defaultProperties), ), fs.writeFile(new URL('texture-layout.json', OUTPUT_ROOT), JSON.stringify(atlasLayout)), fs.writeFile(new URL('texture-atlas.png', OUTPUT_ROOT), downloaded.atlasImage), ]) console.log( `Updated schematic resources to ${EXPECTED_VERSION}: ${Object.keys(definitions).length} block definitions, ${Object.keys(models).length} models, and ${Object.keys(atlasLayout).length} atlas regions (${atlasWidth}x${atlasHeight}).`, )