Created
August 26, 2024 08:47
-
-
Save anatawa12/aa91bb9a34e5ee50830cba18890d42f6 to your computer and use it in GitHub Desktop.
simple tool to shuffle guid of unity asset file in folder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
":" /* | |
exec deno run --allow-read --allow-write "$0" "$@" | |
exit $? | |
":" */ | |
const folder = Deno.args[0]; | |
if (!folder) { | |
console.error("Usage: shuffle-guid-in-folder.ts <folder>"); | |
Deno.exit(1); | |
} | |
const assets = await collectAssets(folder); | |
const guidMap = new Map<string, string>(); | |
for (const asset of assets) { | |
if (asset.guid) { | |
const newGuid = generateGUID(); | |
guidMap.set(asset.guid, newGuid); | |
console.log(`Shuffled GUID: ${asset.guid} -> ${newGuid}`); | |
} | |
} | |
await Promise.all(assets.map(async (asset) => { | |
const promises: Promise<void>[] = []; | |
if (asset.guid) { | |
promises.push(updateGuidOfMetaFile(asset.path, guidMap.get(asset.guid)!)); | |
} | |
if (asset.type == 'file' && asset.isYaml) { | |
promises.push(updateGuidInFile(asset.path, guidMap)); | |
} | |
await Promise.all(promises); | |
})) | |
type Asset = FileAsset | DirectoryAsset; | |
interface FileAsset { | |
type: "file"; | |
path: string; | |
guid?: string | undefined; | |
isYaml: boolean; | |
} | |
interface DirectoryAsset { | |
type: "directory"; | |
path: string; | |
guid?: string | undefined; | |
} | |
async function collectAssets(dir: string): Promise<Asset[]> { | |
const entries = await fromAsync(Deno.readDir(dir)); | |
const metaFiles = entries.filter((entry) => entry.isFile && entry.name.endsWith(".meta")); | |
const assets = entries.filter((entry) => entry.isFile && !entry.name.endsWith(".meta")); | |
const directories = entries.filter((entry) => entry.isDirectory); | |
const childCollects = directories.map((directory) => collectAssets(`${dir}/${directory.name}`)); | |
const guidByPath = new Map<string, string>(); | |
await Promise.all(metaFiles.map(async (metaFile) => { | |
const content = await Deno.readTextFile(`${dir}/${metaFile.name}`); | |
const guidMatch = content.match(/guid: ([a-fA-F0-9]{32})/); | |
if (guidMatch) { | |
guidByPath.set(metaFile.name.slice(0, -5), guidMatch[1]); | |
} | |
})); | |
const fileAssets = assets.map(async (asset) => { | |
const path = `${dir}/${asset.name}`; | |
const guid = guidByPath.get(asset.name); | |
const isYaml = await isFileYaml(path); | |
return { type: "file", path, guid, isYaml } satisfies FileAsset; | |
}); | |
const directoryAssets = directories.map((directory) => { | |
const path = `${dir}/${directory.name}`; | |
const guid = guidByPath.get(directory.name); | |
return { type: "directory", path, guid } satisfies DirectoryAsset; | |
}); | |
return (await Promise.all([...fileAssets, ...childCollects])).flat().concat(directoryAssets); | |
} | |
async function isFileYaml(path: string): Promise<boolean> { | |
const heading = "%YAML"; | |
const file = await Deno.open(path); | |
const buffer = new Uint8Array(heading.length); | |
if (await readFully(file, buffer) !== heading.length) return false; | |
return [...buffer].every((byte, index) => byte === heading.charCodeAt(index)); | |
} | |
async function updateGuidOfMetaFile(path: string, guid: string): Promise<void> { | |
const metaPath = path + ".meta"; | |
const content = await Deno.readTextFile(metaPath); | |
const updatedContent = content.replace(/guid: ([a-fA-F0-9]{32})/, `guid: ${guid}`); | |
await Deno.writeTextFile(metaPath, updatedContent); | |
} | |
async function updateGuidInFile(path: string, guidMap: Map<string, string>): Promise<void> { | |
const content = await Deno.readTextFile(path); | |
const updatedContent = content.replace(/(guid:\s+)([a-fA-F0-9]{32})/g, (_, prefix, guid) => { | |
const newGuid = guidMap.get(guid) ?? guid; | |
return `${prefix}${newGuid}`; | |
}); | |
await Deno.writeTextFile(path, updatedContent); | |
} | |
async function readFully(s: Deno.FsFile, buffer: Uint8Array): Promise<number> { | |
let n = 0; | |
while (n < buffer.length) { | |
const result = await s.read(buffer.subarray(n)); | |
if (result === null) { | |
break; | |
} | |
n += result; | |
} | |
return n; | |
} | |
async function fromAsync<T>(iterable: AsyncIterable<T>): Promise<T[]> { | |
const result: T[] = []; | |
for await (const value of iterable) { | |
result.push(value); | |
} | |
return result; | |
} | |
function generateGUID(): string { | |
const bytes = new Uint8Array(16); | |
crypto.getRandomValues(bytes); | |
return toHex(bytes); | |
} | |
function toHex(bytes: Uint8Array): string { | |
return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join(""); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment