From d641c542272c8e02c3c92c532cf10904a77554d3 Mon Sep 17 00:00:00 2001
From: Gregg Tavares <github@greggman.com>
Date: Sun, 30 Aug 2015 11:41:23 +0100
Subject: [PATCH] don't allow games for a different platform to be installed

So can't install a Unity3D for Windows game on OSX for example.

Added this because drag and drop might make it too easy to try
to install the wrong file
---
 docs/changelist.md                   |   4 +
 lib/gameinfo.js                      |   4 +-
 lib/games.js                         |   2 +-
 management/install.js                | 105 +++++++++++++++++++--------
 management/make-controller-bundle.js |   2 +-
 management/make.js                   |   2 +-
 6 files changed, 83 insertions(+), 36 deletions(-)

diff --git a/docs/changelist.md b/docs/changelist.md
index a4e29aea..24667568 100644
--- a/docs/changelist.md
+++ b/docs/changelist.md
@@ -1,6 +1,10 @@
 Changelist
 ==========
 
+*   0.0.40
+
+    *   don't allow games for a different platform to be installed.
+
 *   0.0.39
 
     *   Games have context menu with "GetInfo", "Uninstall" and/or "Remove"
diff --git a/lib/gameinfo.js b/lib/gameinfo.js
index 74b9df7b..04cfc5b1 100644
--- a/lib/gameinfo.js
+++ b/lib/gameinfo.js
@@ -287,7 +287,7 @@ GameInfo.prototype.checkRequiredFiles = (function() {
     ].concat(controllerChecks),
   };
 
-  return function(runtimeInfo) {
+  return function(runtimeInfo, fileNames) {
     var info = runtimeInfo.info;
     var found = [];
     var foundCount = 0;
@@ -298,7 +298,7 @@ GameInfo.prototype.checkRequiredFiles = (function() {
       checks = checks.concat(gameChecks);
     }
 
-    var fileNames = readdirtree.sync(runtimeInfo.htmlPath);
+    var fileNames = fileNames || readdirtree.sync(runtimeInfo.htmlPath);
     for (var ii = 0; ii < fileNames.length && foundCount < checks.length; ++ii) {
       var fileName = fileNames[ii].replace(/\\/g, '/');
       checks.forEach(function(check, ndx) {  // eslint-disable-line
diff --git a/lib/games.js b/lib/games.js
index 4ac8892f..df971ca0 100644
--- a/lib/games.js
+++ b/lib/games.js
@@ -127,7 +127,7 @@ var InstalledGamesList = function() {
       if (!info) {
         throw "";
       }
-      gameInfo.checkRequiredFiles(info, info.rootPath);
+      gameInfo.checkRequiredFiles(info);
 
       getInstalledGames();
       var index = indexByPath(gamePath);
diff --git a/management/install.js b/management/install.js
index 2a47ff6f..6c8ad52a 100644
--- a/management/install.js
+++ b/management/install.js
@@ -78,6 +78,7 @@ var install = function(releasePath, opt_destPath, opt_options) {
     });
     var runtimeInfo;
     var zipRootPath;
+    var zipPackagePath;
 
     var packageLocations = gameInfo.getPackageLocations();
     var checkPackageLocations = function(entry) {
@@ -106,18 +107,50 @@ var install = function(releasePath, opt_destPath, opt_options) {
       if (!packageEntry) {
         return reject("no package.json found in " + releasePath);
       }
-      zipRootPath = packageEntry.name.replace(/\\/g, "/");
-      zipRootPath = zipRootPath.substring(0, zipRootPath.indexOf("/"));
+      zipPackagePath = packageEntry.name.replace(/\\/g, "/");
+      zipRootPath = zipPackagePath.substring(0, zipPackagePath.indexOf("/"));
       runtimeInfo = gameInfo.parseGameInfo(packageEntry.asText(), packageEntry.name, zipRootPath);
     } catch (e) {
       return reject("could not parse package.json. Maybe this is not a HappyFunTimes game?");
     }
 
+    // Check it's got the required files
+    try {
+      var zipPackageDir = path.dirname(zipPackagePath);
+      var fileNames = entries.map(function(entry) {
+        return path.relative(zipPackageDir, entry.name.replace(/[\\/]/g, path.sep)).replace(/\\/g, "/");
+      });
+      gameInfo.checkRequiredFiles(runtimeInfo, fileNames);
+    } catch (e) {
+      return reject(e);
+    }
+
     var info = runtimeInfo.info;
     var hftInfo = info.happyFunTimes;
     var gameId = runtimeInfo.originalGameId;
+    var safeGameId = releaseUtils.safeishName(gameId);
     var destBasePath;
 
+    var fileExists = {};
+    entries.forEach(function(entry) {
+      var filename = entry.name.replace(/\\/g, "/").replace(/.*?\//, "");
+      fileExists[filename] = true;
+    });
+
+    // Check for more files that should exist.
+    // Games that are "added" don't need this but games
+    // that are "installed" do.
+    log("checking gametype: " + hftInfo.gameType);
+    if (hftInfo.gameType.toLowerCase() === "unity3d") {
+      var exePath = platformInfo.exePath;
+      if (exePath) {
+        exePath = strings.replaceParams(exePath, { gameId: safeGameId });
+        if (!fileExists[exePath]) {
+          return reject("path not found: " + exePath + "\nIs this the correct zip file for " + platformInfo.id + "?");
+        }
+      }
+    }
+
     // is it already installed?
     var installedGame = gameDB.getGameById(gameId);
     if (installedGame) {
@@ -130,8 +163,6 @@ var install = function(releasePath, opt_destPath, opt_options) {
       }
     }
 
-    var safeGameId = releaseUtils.safeishName(gameId);
-
     if (!destBasePath) {
         // make the dir after we're sure we're ready to install
         destBasePath = path.join(config.getConfig().gamesDir, safeGameId);
@@ -139,14 +170,32 @@ var install = function(releasePath, opt_destPath, opt_options) {
 
     destBasePath = opt_destPath ? opt_destPath : destBasePath;
 
+    // Check for bad paths
+    var bad = false;
+    var errors = [];
+    entries.forEach(function(entry) {
+      if (bad) {
+        return;
+      }
+      var filePath = entry.name.substring(zipRootPath.length + 1);
+      var destPath = path.resolve(path.join(destBasePath, filePath));
+      if (destPath.substring(0, destBasePath.length) !== destBasePath) {
+        errors.push("ERROR: bad zip file. Path would write outside game folder");
+        bad = true;
+      }
+    });
+
+    if (bad) {
+      // Should delete all work here?
+      return reject(errors.join("\n"));
+    }
+
     console.log("installing to: " + destBasePath);
     if (!options.dryRun) {
       mkdirp.sync(destBasePath);
     }
 
     var files = [];
-    var errors = [];
-    var bad = false;
     entries.forEach(function(entry) {
       if (bad) {
         return;
@@ -154,31 +203,26 @@ var install = function(releasePath, opt_destPath, opt_options) {
       var filePath = entry.name.substring(zipRootPath.length + 1);
       files.push(filePath);
       var destPath = path.resolve(path.join(destBasePath, filePath));
-      if (destPath.substring(0, destBasePath.length) !== destBasePath) {
-        errors.push("ERROR: bad zip file. Path would write outside game folder");
-        bad = true;
-      } else {
-        var isDir = entry.dir;
-        if (destPath.substr(-1) === "/" || destPath.substr(-1) === "\\") {
-          destPath = destPath.substr(0, destPath.length - 1);
-          isDir = true;
+      var isDir = entry.dir;
+      if (destPath.substr(-1) === "/" || destPath.substr(-1) === "\\") {
+        destPath = destPath.substr(0, destPath.length - 1);
+        isDir = true;
+      }
+      //??
+      if (isDir) {
+        log("mkdir  : " + destPath);
+        if (!options.dryRun) {
+          mkdirp.sync(destPath);
         }
-        //??
-        if (isDir) {
-          log("mkdir  : " + destPath);
-          if (!options.dryRun) {
-            mkdirp.sync(destPath);
-          }
-        } else {
-          log("install: " + entry.name + " -> " + destPath);
-          if (!options.dryRun) {
-            var dirPath = path.dirname(destPath);
-            if (!fs.existsSync(dirPath)) {
-              log("mkdir  : " + dirPath);
-              mkdirp.sync(dirPath);
-            }
-            fs.writeFileSync(destPath, entry.asNodeBuffer());
+      } else {
+        log("install: " + entry.name + " -> " + destPath);
+        if (!options.dryRun) {
+          var dirPath = path.dirname(destPath);
+          if (!fs.existsSync(dirPath)) {
+            log("mkdir  : " + dirPath);
+            mkdirp.sync(dirPath);
           }
+          fs.writeFileSync(destPath, entry.asNodeBuffer());
         }
       }
     });
@@ -188,8 +232,7 @@ var install = function(releasePath, opt_destPath, opt_options) {
       return reject(errors.join("\n"));
     }
 
-    // Should this be in the zip?
-    log("checking gametype: " + hftInfo.gameType);
+    // Set the executable bit
     if (hftInfo.gameType.toLowerCase() === "unity3d") {
       var exePath = platformInfo.exePath;
       if (exePath) {
diff --git a/management/make-controller-bundle.js b/management/make-controller-bundle.js
index 76f4616b..7e1b5a3c 100644
--- a/management/make-controller-bundle.js
+++ b/management/make-controller-bundle.js
@@ -208,7 +208,7 @@ var make = function(gamePath, destFolder, options) {
   }
 
   try {
-    gameInfo.checkRequiredFiles(runtimeInfo, gamePath);
+    gameInfo.checkRequiredFiles(runtimeInfo);
     if (!fs.existsSync(destFolder)) {
       fs.mkdirSync(destFolder);
     }
diff --git a/management/make.js b/management/make.js
index b4a1c09c..9102f13a 100644
--- a/management/make.js
+++ b/management/make.js
@@ -320,7 +320,7 @@ var make = function(gamePath, destFolder, options) {
   }
 
   try {
-    gameInfo.checkRequiredFiles(runtimeInfo, gamePath);
+    gameInfo.checkRequiredFiles(runtimeInfo);
     if (!fs.existsSync(destFolder)) {
       fs.mkdirSync(destFolder);
     }