Fix cache size and switch to using fileAnalysis for bundle sizes

This commit is contained in:
Natsumi
2025-08-10 22:54:31 +12:00
parent d40d0af21f
commit e182b4f2c2
9 changed files with 102 additions and 150 deletions

View File

@@ -28,16 +28,14 @@ namespace VRCX
public string GetAssetId(string id, string variant = "") public string GetAssetId(string id, string variant = "")
{ {
using (var sha256 = SHA256.Create()) using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(id + variant));
var idHex = new StringBuilder(hash.Length * 2);
foreach (var b in hash)
{ {
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(id + variant)); idHex.Append($"{b:x2}");
StringBuilder idHex = new StringBuilder(hash.Length * 2);
foreach (byte b in hash)
{
idHex.AppendFormat("{0:x2}", b);
}
return idHex.ToString().ToUpper().Substring(0, 16);
} }
return idHex.ToString().ToUpper().Substring(0, 16);
} }
public string GetAssetVersion(int version, int variantVersion = 0) public string GetAssetVersion(int version, int variantVersion = 0)
@@ -136,7 +134,7 @@ namespace VRCX
if (File.Exists(fileLocation)) if (File.Exists(fileLocation))
{ {
cachePath = fullLocation; cachePath = fullLocation;
FileInfo data = new FileInfo(fileLocation); var data = new FileInfo(fileLocation);
fileSize = data.Length; fileSize = data.Length;
} }
if (File.Exists(Path.Join(fullLocation, "__lock"))) if (File.Exists(Path.Join(fullLocation, "__lock")))
@@ -241,8 +239,8 @@ namespace VRCX
public long DirSize(DirectoryInfo d) public long DirSize(DirectoryInfo d)
{ {
long size = 0; long size = 0;
FileInfo[] files = d.GetFiles("*.*", SearchOption.AllDirectories); var files = d.GetFiles("*.*", SearchOption.AllDirectories);
foreach (FileInfo file in files) foreach (var file in files)
{ {
size += file.Length; size += file.Length;
} }

View File

@@ -217,8 +217,7 @@ namespace VRCX
{ {
var fileHashBytes = await sha256.ComputeHashAsync(stream, _cancellationToken); var fileHashBytes = await sha256.ComputeHashAsync(stream, _cancellationToken);
var fileHashString = Convert.ToHexString(fileHashBytes); var fileHashString = Convert.ToHexString(fileHashBytes);
if (!string.IsNullOrEmpty(fileHashString) && if (!hashString.Equals(fileHashString, StringComparison.OrdinalIgnoreCase))
!hashString.Equals(fileHashString, StringComparison.OrdinalIgnoreCase))
{ {
logger.Error($"Hash check failed file:{fileHashString} web:{hashString}"); logger.Error($"Hash check failed file:{fileHashString} web:{hashString}");
throw new Exception("Hash check failed"); throw new Exception("Hash check failed");

View File

@@ -122,7 +122,7 @@ const avatarReq = {
/** /**
* @param {{ avatarId: string }} params * @param {{ avatarId: string }} params
* @returns {Promise<{json: T, params}>} * @returns {Promise<{json: any, params}>}
*/ */
deleteImposter(params) { deleteImposter(params) {
return request(`avatars/${params.avatarId}/impostor`, { return request(`avatars/${params.avatarId}/impostor`, {
@@ -151,7 +151,7 @@ const avatarReq = {
}, },
/** /**
* @param {{ avatarId: string }} avatarId * @param {string} avatarId
* @returns {Promise<{json: any, params}>} * @returns {Promise<{json: any, params}>}
*/ */
getAvatarGallery(avatarId) { getAvatarGallery(avatarId) {
@@ -174,7 +174,7 @@ const avatarReq = {
}, },
/** /**
* @param {{ imageData: string, avatarId: string }} params * @param {{ imageData: string, avatarId: string }}
* @returns {Promise<{json: any, params}>} * @returns {Promise<{json: any, params}>}
*/ */
uploadAvatarGalleryImage(imageData, avatarId) { uploadAvatarGalleryImage(imageData, avatarId) {

View File

@@ -553,18 +553,6 @@
icon="el-icon-refresh" icon="el-icon-refresh"
circle circle
@click="refreshAvatarDialogTreeData"></el-button> @click="refreshAvatarDialogTreeData"></el-button>
<el-tooltip
placement="top"
:content="t('dialog.avatar.json.file_analysis')"
:disabled="hideTooltips">
<el-button
type="default"
size="mini"
icon="el-icon-s-data"
circle
style="margin-left: 5px"
@click="getAvatarFileAnalysis"></el-button>
</el-tooltip>
<el-button <el-button
type="default" type="default"
size="mini" size="mini"
@@ -573,8 +561,8 @@
style="margin-left: 5px" style="margin-left: 5px"
@click="downloadAndSaveJson(avatarDialog.id, avatarDialog.ref)"></el-button> @click="downloadAndSaveJson(avatarDialog.id, avatarDialog.ref)"></el-button>
<el-tree <el-tree
v-if="Object.keys(fileAnalysis).length > 0" v-if="Object.keys(avatarDialog.fileAnalysis).length > 0"
:data="fileAnalysis" :data="avatarDialog.fileAnalysis"
style="margin-top: 5px; font-size: 12px"> style="margin-top: 5px; font-size: 12px">
<template #default="scope"> <template #default="scope">
<span> <span>
@@ -665,7 +653,6 @@
const treeData = ref([]); const treeData = ref([]);
const timeSpent = ref(0); const timeSpent = ref(0);
const memo = ref(''); const memo = ref('');
const fileAnalysis = ref({});
const setAvatarTagsDialog = reactive({ const setAvatarTagsDialog = reactive({
visible: false, visible: false,
loading: false, loading: false,
@@ -740,7 +727,7 @@
} }
function handleDialogOpen() { function handleDialogOpen() {
fileAnalysis.value = {}; avatarDialog.value.fileAnalysis = {};
memo.value = ''; memo.value = '';
treeData.value = []; treeData.value = [];
getAvatarTimeSpent(); getAvatarTimeSpent();
@@ -1085,66 +1072,6 @@
treeData.value = buildTreeData(avatarDialog.value.ref); treeData.value = buildTreeData(avatarDialog.value.ref);
} }
function getAvatarFileAnalysis() {
let unityPackage;
const D = avatarDialog.value;
const avatarId = D.ref.id;
let assetUrl = '';
let variant = 'security';
for (let i = D.ref.unityPackages.length - 1; i > -1; i--) {
unityPackage = D.ref.unityPackages[i];
if (unityPackage.variant !== 'security') {
continue;
}
if (unityPackage.platform === 'standalonewindows' && compareUnityVersion(unityPackage.unitySortNumber)) {
assetUrl = unityPackage.assetUrl;
break;
}
}
if (!assetUrl) {
for (let i = D.ref.unityPackages.length - 1; i > -1; i--) {
unityPackage = D.ref.unityPackages[i];
if (unityPackage.variant !== 'standard') {
continue;
}
if (
unityPackage.platform === 'standalonewindows' &&
compareUnityVersion(unityPackage.unitySortNumber)
) {
variant = 'standard';
assetUrl = unityPackage.assetUrl;
break;
}
}
}
const fileId = extractFileId(assetUrl);
const version = parseInt(extractFileVersion(assetUrl), 10);
if (!fileId || !version) {
$message({
message: 'File Analysis unavailable',
type: 'error'
});
return;
}
miscRequest.getFileAnalysis({ fileId, version, variant }).then((args) => {
if (!avatarDialog.value.visible || avatarDialog.value.id !== avatarId) {
return;
}
const ref = args.json;
if (typeof ref.fileSize !== 'undefined') {
ref._fileSize = `${(ref.fileSize / 1048576).toFixed(2)} MB`;
}
if (typeof ref.uncompressedSize !== 'undefined') {
ref._uncompressedSize = `${(ref.uncompressedSize / 1048576).toFixed(2)} MB`;
}
if (typeof ref.avatarStats?.totalTextureUsage !== 'undefined') {
ref._totalTextureUsage = `${(ref.avatarStats.totalTextureUsage / 1048576).toFixed(2)} MB`;
}
fileAnalysis.value = buildTreeData(args.json);
});
}
function showSetAvatarTagsDialog(avatarId) { function showSetAvatarTagsDialog(avatarId) {
const D = setAvatarTagsDialog; const D = setAvatarTagsDialog;
D.visible = true; D.visible = true;

View File

@@ -722,6 +722,17 @@
circle circle
style="margin-left: 5px" style="margin-left: 5px"
@click="downloadAndSaveJson(worldDialog.id, worldDialog.ref)"></el-button> @click="downloadAndSaveJson(worldDialog.id, worldDialog.ref)"></el-button>
<el-tree
v-if="Object.keys(worldDialog.fileAnalysis).length > 0"
:data="worldDialog.fileAnalysis"
style="margin-top: 5px; font-size: 12px">
<template #default="scope">
<span>
<span style="font-weight: bold; margin-right: 5px" v-text="scope.data.key"></span>
<span v-if="!scope.data.children" v-text="scope.data.value"></span>
</span>
</template>
</el-tree>
<el-tree :data="treeData" style="margin-top: 5px; font-size: 12px"> <el-tree :data="treeData" style="margin-top: 5px; font-size: 12px">
<template #default="{ data }"> <template #default="{ data }">
<span> <span>

View File

@@ -267,7 +267,7 @@ const gameLog = {
async getVisitCount(worldId) { async getVisitCount(worldId) {
var ref = { var ref = {
visitCount: '', visitCount: 0,
worldId: '' worldId: ''
}; };
await sqliteService.execute( await sqliteService.execute(

View File

@@ -86,7 +86,9 @@ async function deleteVRChatCache(ref) {
compareUnityVersion(unityPackage.unitySortNumber) compareUnityVersion(unityPackage.unitySortNumber)
) { ) {
assetUrl = unityPackage.assetUrl; assetUrl = unityPackage.assetUrl;
if (unityPackage.variant !== 'standard') { if (!unityPackage.variant || unityPackage.variant === 'standard') {
variant = 'security';
} else {
variant = unityPackage.variant; variant = unityPackage.variant;
} }
break; break;
@@ -119,7 +121,9 @@ async function checkVRChatCache(ref) {
compareUnityVersion(unityPackage.unitySortNumber) compareUnityVersion(unityPackage.unitySortNumber)
) { ) {
assetUrl = unityPackage.assetUrl; assetUrl = unityPackage.assetUrl;
if (unityPackage.variant !== 'standard') { if (!unityPackage.variant || unityPackage.variant === 'standard') {
variant = 'security';
} else {
variant = unityPackage.variant; variant = unityPackage.variant;
} }
break; break;
@@ -424,6 +428,7 @@ async function getBundleDateSize(ref) {
const { currentInstanceWorld, currentInstanceLocation } = const { currentInstanceWorld, currentInstanceLocation } =
storeToRefs(instanceStore); storeToRefs(instanceStore);
const bundleSizes = []; const bundleSizes = [];
const bundleJson = [];
for (let i = ref.unityPackages.length - 1; i > -1; i--) { for (let i = ref.unityPackages.length - 1; i > -1; i--) {
const unityPackage = ref.unityPackages[i]; const unityPackage = ref.unityPackages[i];
if ( if (
@@ -443,59 +448,63 @@ async function getBundleDateSize(ref) {
} }
const assetUrl = unityPackage.assetUrl; const assetUrl = unityPackage.assetUrl;
const fileId = extractFileId(assetUrl); const fileId = extractFileId(assetUrl);
const fileVersion = parseInt(extractFileVersion(assetUrl), 10); const version = parseInt(extractFileVersion(assetUrl), 10);
if (!fileId) { let variant = '';
if (!unityPackage.variant || unityPackage.variant === 'standard') {
variant = 'security';
} else {
variant = unityPackage.variant;
}
if (!fileId || !version) {
continue; continue;
} }
const args = await miscRequest.getBundles(fileId); console.log(
if (!args?.json?.versions) { `Fetching bundle size for ${platform} - fileId: ${fileId}, version: ${version}, variant: ${variant}`
);
const args = await miscRequest.getFileAnalysis({
fileId,
version,
variant
});
if (!args?.json?.success) {
continue; continue;
} }
let { versions } = args.json; const json = args.json;
for (let j = versions.length - 1; j > -1; j--) { if (typeof json.fileSize !== 'undefined') {
const version = versions[j]; json._fileSize = `${(json.fileSize / 1048576).toFixed(2)} MB`;
if (version.version === fileVersion) { }
const createdAt = version.created_at; if (typeof json.uncompressedSize !== 'undefined') {
const fileSize = `${( json._uncompressedSize = `${(json.uncompressedSize / 1048576).toFixed(2)} MB`;
version.file.sizeInBytes / 1048576 }
).toFixed(2)} MB`; if (typeof json.avatarStats?.totalTextureUsage !== 'undefined') {
bundleSizes[platform] = { json._totalTextureUsage = `${(json.avatarStats.totalTextureUsage / 1048576).toFixed(2)} MB`;
createdAt, }
fileSize bundleJson[platform] = json;
}; const createdAt = json.created_at;
const fileSize = `${(json.fileSize / 1048576).toFixed(2)} MB`;
bundleSizes[platform] = {
createdAt,
fileSize
};
// update avatar dialog // update avatar dialog
if (avatarDialog.value.id === ref.id) { if (avatarDialog.value.id === ref.id) {
avatarDialog.value.bundleSizes[platform] = avatarDialog.value.bundleSizes[platform] = bundleSizes[platform];
bundleSizes[platform]; avatarDialog.value.lastUpdated = createdAt;
if (avatarDialog.value.lastUpdated < version.created_at) { avatarDialog.value.fileAnalysis = buildTreeData(bundleJson);
avatarDialog.value.lastUpdated = version.created_at; }
} // update world dialog
} if (worldDialog.value.id === ref.id) {
// update world dialog worldDialog.value.bundleSizes[platform] = bundleSizes[platform];
if (worldDialog.value.id === ref.id) { worldDialog.value.lastUpdated = createdAt;
worldDialog.value.bundleSizes[platform] = worldDialog.value.fileAnalysis = buildTreeData(bundleJson);
bundleSizes[platform]; }
if (worldDialog.value.lastUpdated < version.created_at) { // update player list
worldDialog.value.lastUpdated = version.created_at; if (currentInstanceLocation.value.worldId === ref.id) {
} currentInstanceWorld.value.bundleSizes[platform] =
} bundleSizes[platform];
// update player list currentInstanceWorld.value.lastUpdated = createdAt;
if (currentInstanceLocation.value.worldId === ref.id) {
currentInstanceWorld.value.bundleSizes[platform] =
bundleSizes[platform];
if (
currentInstanceWorld.value.lastUpdated <
version.created_at
) {
currentInstanceWorld.value.lastUpdated =
version.created_at;
}
}
break;
}
} }
} }

View File

@@ -50,9 +50,10 @@ export const useAvatarStore = defineStore('Avatar', () => {
galleryLoading: false, galleryLoading: false,
lastUpdated: '', lastUpdated: '',
inCache: false, inCache: false,
cacheSize: 0, cacheSize: '',
cacheLocked: false, cacheLocked: false,
cachePath: '' cachePath: '',
fileAnalysis: {}
}, },
cachedAvatarModerations: new Map(), cachedAvatarModerations: new Map(),
avatarHistory: new Set(), avatarHistory: new Set(),
@@ -131,9 +132,12 @@ export const useAvatarStore = defineStore('Avatar', () => {
highestPrice: null, highestPrice: null,
id: '', id: '',
imageUrl: '', imageUrl: '',
listingDate: null,
lock: false, lock: false,
lowestPrice: null, lowestPrice: null,
name: '', name: '',
pendingUpload: false,
performance: {},
productId: null, productId: null,
publishedListings: [], publishedListings: [],
releaseStatus: '', releaseStatus: '',
@@ -205,9 +209,10 @@ export const useAvatarStore = defineStore('Avatar', () => {
D.loading = true; D.loading = true;
D.id = avatarId; D.id = avatarId;
D.inCache = false; D.inCache = false;
D.cacheSize = 0; D.cacheSize = '';
D.cacheLocked = false; D.cacheLocked = false;
D.cachePath = ''; D.cachePath = '';
D.fileAnalysis = {};
D.isQuestFallback = false; D.isQuestFallback = false;
D.isPC = false; D.isPC = false;
D.isQuest = false; D.isQuest = false;
@@ -350,7 +355,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
const D = state.avatarDialog; const D = state.avatarDialog;
if (D.visible) { if (D.visible) {
D.inCache = false; D.inCache = false;
D.cacheSize = 0; D.cacheSize = '';
D.cacheLocked = false; D.cacheLocked = false;
D.cachePath = ''; D.cachePath = '';
checkVRChatCache(D.ref).then((cacheInfo) => { checkVRChatCache(D.ref).then((cacheInfo) => {

View File

@@ -39,9 +39,10 @@ export const useWorldStore = defineStore('World', () => {
bundleSizes: [], bundleSizes: [],
lastUpdated: '', lastUpdated: '',
inCache: false, inCache: false,
cacheSize: 0, cacheSize: '',
cacheLocked: false, cacheLocked: false,
cachePath: '', cachePath: '',
fileAnalysis: {},
lastVisit: '', lastVisit: '',
visitCount: 0, visitCount: 0,
timeSpent: 0, timeSpent: 0,
@@ -96,11 +97,13 @@ export const useWorldStore = defineStore('World', () => {
D.visible = true; D.visible = true;
D.loading = true; D.loading = true;
D.inCache = false; D.inCache = false;
D.cacheSize = 0; D.cacheSize = '';
D.cacheLocked = false; D.cacheLocked = false;
D.cachePath = '';
D.fileAnalysis = {};
D.rooms = []; D.rooms = [];
D.lastVisit = ''; D.lastVisit = '';
D.visitCount = ''; D.visitCount = 0;
D.timeSpent = 0; D.timeSpent = 0;
D.isFavorite = false; D.isFavorite = false;
D.avatarScalingDisabled = false; D.avatarScalingDisabled = false;
@@ -211,7 +214,7 @@ export const useWorldStore = defineStore('World', () => {
const D = state.worldDialog; const D = state.worldDialog;
if (D.visible) { if (D.visible) {
D.inCache = false; D.inCache = false;
D.cacheSize = 0; D.cacheSize = '';
D.cacheLocked = false; D.cacheLocked = false;
D.cachePath = ''; D.cachePath = '';
checkVRChatCache(D.ref).then((cacheInfo) => { checkVRChatCache(D.ref).then((cacheInfo) => {