Spaces:
Sleeping
Sleeping
File size: 3,999 Bytes
35aee1c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
import optimizeSb3Json from './minify/sb3';
import {downloadProjectFromBuffer} from '@turbowarp/sbdl';
const unknownAnalysis = () => ({
stageVariables: [],
stageComments: [],
usesMusic: true,
extensions: []
});
const analyzeScratch2 = (projectData) => {
const stageVariables = (projectData.variables || [])
.map(({name, isPersistent}) => ({
name,
isCloud: isPersistent
}));
// This may have some false positives, but that's okay.
const stringified = JSON.stringify(projectData);
const usesMusic = stringified.includes('drum:duration:elapsed:from:') ||
stringified.includes('playDrum') ||
stringified.includes('noteOn:duration:elapsed:from:');
return {
...unknownAnalysis(),
stageVariables,
usesMusic
};
};
const analyzeScratch3 = (projectData) => {
const stage = projectData.targets[0];
if (!stage || !stage.isStage) {
throw new Error('Project does not have stage');
}
const stageVariables = Object.values(stage.variables)
.map(([name, _value, cloud]) => ({
name,
isCloud: !!cloud
}));
const stageComments = Object.values(stage.comments)
.map((i) => i.text);
// TODO: usesMusic has possible false negatives
const usesMusic = projectData.extensions.includes('music');
const extensions = projectData.extensionURLs ? Object.values(projectData.extensionURLs) : [];
return {
...unknownAnalysis(),
stageVariables,
stageComments,
usesMusic,
extensions
};
};
const mutateScratch3InPlace = (projectData) => {
const makeImpliedCloudVariables = (projectData) => {
const stage = projectData.targets.find((i) => i.isStage);
if (stage) {
for (const variable of Object.values(stage.variables)) {
const name = variable[0];
if (name.startsWith('☁')) {
variable[2] = true;
}
}
}
};
const disableNonsenseCloudVariables = (projectData) => {
const DISABLE_CLOUD_VARIABLES = [
// The "original" Sprunki project includes a cloud variable presumably used to detect who
// clicked on the report button. That seems like a Scratch community guidelines violation but
// that's not our job to enforce. This affects us because these games are very popular and
// create thousands of unnecessary concurrent cloud variable connections for a feature that
// can't work because there is no report button to click on.
'☁ potential reporters'
];
// I want a more general solution here that automatically disables all unused cloud variables,
// but making that work in the presence of various unknown extensions seems non-trivial.
const stage = projectData.targets.find((i) => i.isStage);
if (stage) {
for (const variable of Object.values(stage.variables)) {
// variable is [name, value, isCloud]
if (variable[2] && DISABLE_CLOUD_VARIABLES.includes(variable[0])) {
variable[2] = false;
}
}
}
};
// Order matters -- check for implied cloud variables before disabling some of them.
makeImpliedCloudVariables(projectData);
disableNonsenseCloudVariables(projectData);
optimizeSb3Json(projectData);
};
export const downloadProject = async (projectData, progressCallback = () => {}, signal) => {
let analysis = unknownAnalysis();
const options = {
signal,
onProgress(type, loaded, total) {
progressCallback(type, loaded, total);
},
processJSON(type, projectData) {
if (type === 'sb3' || type === 'pm' || type === 'pmp' || type === 's4stxt') {
mutateScratch3InPlace(projectData);
analysis = analyzeScratch3(projectData);
return projectData;
}
if (type === 'sb2') {
analysis = analyzeScratch2(projectData);
}
}
};
const project = await downloadProjectFromBuffer(projectData, options);
if (project.type !== 'sb3' || project.type === 'pm') {
project.type = 'blob';
}
project.analysis = analysis;
return project;
};
|