diff --git a/src-main/l10n/en.json b/src-main/l10n/en.json index 862cfc7d..d988cca7 100644 --- a/src-main/l10n/en.json +++ b/src-main/l10n/en.json @@ -444,11 +444,11 @@ "developer_comment": "Part of the extension security prompt window" }, "unsafe-path.title": { - "string": "Invalid file location", - "developer_comment": "Title of alert that appears when someone tries to save project in an 'unsafe' place where their work will be deleted." + "string": "Invalid File Location", + "developer_comment": "Title of alert that appears when someone tries to save project in an 'unsafe' place where their work would end up being deleted." }, "unsafe-path.details": { - "string": "The file you selected ({file}) is in an internal folder used by {APP_NAME}. Everything in this folder is deleted each time the app updates. You must pick a different path.", - "developer_comment": "Message of alert that appears when someone tries to save project in a folder used internally by the app. {file} is replaced with the path." + "string": "The file you selected ({file}) is in a folder used internally by {APP_NAME}. It is not safe to save projects here as they can be deleted when the app updates. You must choose a different folder.", + "developer_comment": "Message of alert that appears when someone tries to save project in a folder used internally by the app. {file} is replaced with the path and {APP_NAME} with the name of the app." } } \ No newline at end of file diff --git a/src-main/windows/editor.js b/src-main/windows/editor.js index d0dd97eb..ef52ac00 100644 --- a/src-main/windows/editor.js +++ b/src-main/windows/editor.js @@ -130,6 +130,60 @@ const parseOpenedFile = (file, workingDirectory) => { return new OpenedFile(TYPE_FILE, path.resolve(workingDirectory, file)); }; +/** + * @returns {Array<{path: string; app: string;}>} + */ +const getUnsafePaths = () => { + if (process.platform !== 'win32') { + // This problem doesn't really exist on other platforms + return []; + } + + const localPrograms = path.join(app.getPath('home'), 'AppData', 'Local', 'Programs'); + const appData = app.getPath('appData'); + return [ + // Current app, regardless of where it is installed or how modded it is + { + path: path.dirname(app.getPath('exe')), + app: APP_NAME, + }, + { + path: path.dirname(app.getPath('userData')), + app: APP_NAME, + }, + + // TurboWarp Desktop defaults + { + path: path.join(appData, 'turbowarp-desktop'), + app: 'TurboWarp Desktop' + }, + { + path: path.join(localPrograms, 'TurboWarp'), + app: 'TurboWarp Desktop' + }, + + // Scratch Desktop defaults + { + path: path.join(appData, 'Scratch'), + app: 'Scratch Desktop' + }, + { + path: path.join(localPrograms, 'Scratch 3'), + app: 'Scratch Desktop' + } + ]; +}; + +/** + * @param {string} parent + * @param {string} child + * @returns {boolean} + */ +const isChildPath = (parent, child) => { + const relative = path.relative(parent, child); + return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative); +}; + class EditorWindow extends ProjectRunningWindow { /** * @param {OpenedFile|null} file @@ -290,17 +344,16 @@ class EditorWindow extends ProjectRunningWindow { const file = result.filePath; - // Refuse to let people save new projects in our installation directory as it is - // deleted each time the app updates. - const installDir = path.dirname(app.getPath('exe')); - const pathToInstallDir = path.relative(installDir, file); - if (pathToInstallDir && !pathToInstallDir.startsWith('..') && !path.isAbsolute(pathToInstallDir)) { + const unsafePath = getUnsafePaths().find(i => isChildPath(i.path, file)); + if (unsafePath) { // No need to wait for the message box to close dialog.showMessageBox(this.window, { type: 'error', title: APP_NAME, message: translate('unsafe-path.title'), - detail: translate(`unsafe-path.details`).replace('{APP_NAME}', APP_NAME).replace('{file}', file), + detail: translate(`unsafe-path.details`) + .replace('{APP_NAME}', unsafePath.app) + .replace('{file}', file), noLink: true }); return null;