mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 17:59:51 +02:00
139 lines
4.2 KiB
JavaScript
139 lines
4.2 KiB
JavaScript
import { deepStrictEqual } from 'assert'
|
||
import { get } from 'https'
|
||
import { parse } from 'csv-parse/sync'
|
||
import { deflateSync } from 'zlib'
|
||
import { writeFileSync } from 'fs'
|
||
import { dirname, resolve } from 'path'
|
||
import { fileURLToPath } from 'url'
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||
|
||
function pullString(url) {
|
||
return new Promise((resolve, reject) => {
|
||
get(url, (res) => {
|
||
if (res.statusCode !== 200) {
|
||
reject(new Error('unexpected status code'))
|
||
res.resume()
|
||
return
|
||
}
|
||
|
||
const buffers = []
|
||
|
||
res
|
||
.on('data', buffers.push.bind(buffers))
|
||
.on('end', () => {
|
||
resolve(Buffer.concat(buffers))
|
||
})
|
||
.on('error', reject)
|
||
}).on('error', reject)
|
||
})
|
||
}
|
||
|
||
async function main() {
|
||
const data =
|
||
(await pullString('https://storage.googleapis.com/play_public/supported_devices.csv'))
|
||
.toString('utf-16le')
|
||
|
||
const parsedData = parse(data, {})
|
||
|
||
deepStrictEqual(parsedData[0], ['Retail Branding', 'Marketing Name', 'Device', 'Model'])
|
||
|
||
const rows = parsedData.slice(1).map((row) => {
|
||
if (row.length !== 4) throw new Error(`got ${row.length} columns: ${row}`)
|
||
|
||
const [brand, marketingName, device, model] = row
|
||
|
||
return { brand, marketingName, device, model }
|
||
})
|
||
|
||
const relevantRows = rows.filter((row) => row.marketingName !== row.model)
|
||
|
||
relevantRows.sort((a, b) => {
|
||
if (a.device < b.device) return -1
|
||
else if (a.device > b.device) return 1
|
||
else if (a.model < b.model) return -1
|
||
else if (a.model > b.model) return 1
|
||
else return 0
|
||
})
|
||
|
||
const relevantStringsSet = new Set()
|
||
|
||
relevantRows.forEach((row) => {
|
||
relevantStringsSet.add(row.device)
|
||
relevantStringsSet.add(row.model)
|
||
relevantStringsSet.add(row.marketingName)
|
||
})
|
||
|
||
const relevantStrings = Array.from(relevantStringsSet)
|
||
relevantStrings.sort(); relevantStrings.reverse()
|
||
|
||
const stringToIndex = new Map()
|
||
|
||
let relevantStringPool = Buffer.alloc(0)
|
||
let relevantStringPoolAddCounter = 0
|
||
for (const str of relevantStrings) {
|
||
if (stringToIndex.has(str)) continue
|
||
|
||
const oldIndex = relevantStringPool.indexOf(str)
|
||
|
||
if (oldIndex === -1) {
|
||
stringToIndex.set(str, relevantStringPool.length)
|
||
|
||
relevantStringPool = Buffer.concat([relevantStringPool, Buffer.from(str)])
|
||
relevantStringPoolAddCounter++
|
||
} else {
|
||
stringToIndex.set(str, oldIndex)
|
||
}
|
||
}
|
||
|
||
// 4 bytes per string: start index (3 byte) + length (1 byte)
|
||
// 3 strings (device, model, marketingName)
|
||
const deviceInfoBuffer = Buffer.alloc(relevantRows.length * (3 * 4))
|
||
|
||
const writeString = (outputIndex, str) => {
|
||
const startIndex = stringToIndex.get(str)
|
||
|
||
if (startIndex === undefined) throw new Error()
|
||
if (startIndex > 0xffffff) throw new Error()
|
||
deviceInfoBuffer.writeUInt8((startIndex >> 16) & 0xff, outputIndex * 4 + 0)
|
||
deviceInfoBuffer.writeUInt8((startIndex >> 8) & 0xff, outputIndex * 4 + 1)
|
||
deviceInfoBuffer.writeUInt8(startIndex & 0xff, outputIndex * 4 + 2)
|
||
deviceInfoBuffer.writeUInt8(str.length, outputIndex * 4 + 3)
|
||
}
|
||
|
||
relevantRows.forEach((row, index) => {
|
||
const outputIndex = index * 3
|
||
|
||
writeString(outputIndex + 0, row.device)
|
||
writeString(outputIndex + 1, row.model)
|
||
writeString(outputIndex + 2, row.marketingName)
|
||
})
|
||
|
||
const relevantStringBuffer = Buffer.from(relevantStringPool, 'utf8')
|
||
|
||
const lengthDataBuffer = Buffer.alloc(8)
|
||
lengthDataBuffer.writeUInt32BE(relevantStringBuffer.length, 0)
|
||
lengthDataBuffer.writeUInt32BE(relevantRows.length, 4)
|
||
|
||
const resultData = Buffer.concat([lengthDataBuffer, relevantStringBuffer, deviceInfoBuffer])
|
||
const resultDataCompressed = deflateSync(resultData, {})
|
||
|
||
writeFileSync(resolve(__dirname, '../app/src/main/assets/device-names.bin'), resultDataCompressed)
|
||
|
||
console.log({
|
||
rows: rows.length,
|
||
relevantRows: relevantRows.length,
|
||
relevantStringCount: relevantStrings.length,
|
||
relevantStringPoolAddCounter,
|
||
relevantStringPoolLength: relevantStringPool.length,
|
||
resultDataLength: resultData.length,
|
||
resultDataCompressedLength: resultDataCompressed.length,
|
||
compressedBytesPerDevices: resultDataCompressed.length / relevantRows.length
|
||
})
|
||
}
|
||
|
||
main().catch((ex) => {
|
||
console.warn(ex)
|
||
|
||
process.exit(1)
|
||
})
|