Skip to content

Instantly share code, notes, and snippets.

@anatawa12
Created August 26, 2024 08:47
Show Gist options
  • Save anatawa12/aa91bb9a34e5ee50830cba18890d42f6 to your computer and use it in GitHub Desktop.
Save anatawa12/aa91bb9a34e5ee50830cba18890d42f6 to your computer and use it in GitHub Desktop.
simple tool to shuffle guid of unity asset file in folder
#!/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