Initial commit

This commit is contained in:
AnRil
2026-05-16 13:43:29 +07:00
commit 688a86b611
208 changed files with 44350 additions and 0 deletions

122
src/main/games/vdf.ts Normal file
View File

@@ -0,0 +1,122 @@
// Minimal Valve KeyValues (VDF) text parser.
// Handles nested objects and quoted string values. Sufficient for libraryfolders.vdf.
export type VdfNode = { [key: string]: string | VdfNode }
class Cursor {
constructor(public src: string, public pos: number = 0) {}
peek(): string {
return this.src[this.pos] ?? ''
}
next(): string {
return this.src[this.pos++] ?? ''
}
eof(): boolean {
return this.pos >= this.src.length
}
}
function skipWhitespaceAndComments(c: Cursor): void {
for (;;) {
while (!c.eof() && /\s/.test(c.peek())) c.next()
if (c.peek() === '/' && c.src[c.pos + 1] === '/') {
while (!c.eof() && c.next() !== '\n') {
/* skip line */
}
continue
}
return
}
}
function readToken(c: Cursor): string {
skipWhitespaceAndComments(c)
if (c.eof()) return ''
if (c.peek() === '"') {
c.next()
let out = ''
while (!c.eof()) {
const ch = c.next()
if (ch === '\\') {
const next = c.next()
if (next === 'n') out += '\n'
else if (next === 't') out += '\t'
else out += next
continue
}
if (ch === '"') return out
out += ch
}
return out
}
if (c.peek() === '{' || c.peek() === '}') return c.next()
let out = ''
while (!c.eof() && !/\s/.test(c.peek()) && c.peek() !== '{' && c.peek() !== '}') {
out += c.next()
}
return out
}
function parseObject(c: Cursor): VdfNode {
const node: VdfNode = {}
for (;;) {
skipWhitespaceAndComments(c)
if (c.eof()) return node
if (c.peek() === '}') {
c.next()
return node
}
const key = readToken(c)
if (!key) return node
skipWhitespaceAndComments(c)
if (c.peek() === '{') {
c.next()
node[key] = parseObject(c)
} else {
node[key] = readToken(c)
}
}
}
export function parseVdf(src: string): VdfNode {
const c = new Cursor(src)
const root: VdfNode = {}
for (;;) {
skipWhitespaceAndComments(c)
if (c.eof()) break
const key = readToken(c)
if (!key) break
skipWhitespaceAndComments(c)
if (c.peek() === '{') {
c.next()
root[key] = parseObject(c)
} else {
root[key] = readToken(c)
}
}
return root
}
function escapeVdfString(s: string): string {
return s
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
}
export function stringifyVdf(node: VdfNode, indent: number = 0): string {
const pad = '\t'.repeat(indent)
let out = ''
for (const key of Object.keys(node)) {
const value = node[key]
if (typeof value === 'string') {
out += `${pad}"${escapeVdfString(key)}"\t\t"${escapeVdfString(value)}"\n`
} else {
out += `${pad}"${escapeVdfString(key)}"\n${pad}{\n`
out += stringifyVdf(value, indent + 1)
out += `${pad}}\n`
}
}
return out
}