Browse Source

Merge pull request #2494 from IvanSavenko/hotfix_2

Hotfix 2
Ivan Savenko 2 years ago
parent
commit
1826e990d1

+ 1 - 1
android/vcmi-app/build.gradle

@@ -10,7 +10,7 @@ android {
 		applicationId "is.xyz.vcmi"
 		minSdk 19
 		targetSdk 31
-		versionCode 1302
+		versionCode 1303
 		versionName "1.3.0"
 		setProperty("archivesBaseName", "vcmi")
 	}

+ 1 - 1
android/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java

@@ -146,7 +146,7 @@ public class NativeMethods
     public static void hapticFeedback()
     {
         final Context ctx = SDL.getContext();
-        if (Build.VERSION.SDK_INT >= 26) {
+        if (Build.VERSION.SDK_INT >= 29) {
             ((Vibrator) ctx.getSystemService(ctx.VIBRATOR_SERVICE)).vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK));
         } else {
             ((Vibrator) ctx.getSystemService(ctx.VIBRATOR_SERVICE)).vibrate(30);

+ 6 - 0
android/vcmi-app/src/main/java/eu/vcmi/vcmi/settings/ExportDataController.java

@@ -133,6 +133,12 @@ public class ExportDataController extends LauncherSettingController<Void, Void>
                         }
                     }
 
+                    if (exported == null)
+                    {
+                        publishProgress("Failed to copy file " + child.getName());
+                        return false;
+                    }
+
                     try(
                             final OutputStream targetStream = owner.getContentResolver()
                                     .openOutputStream(exported.getUri());

+ 7 - 1
android/vcmi-app/src/main/java/eu/vcmi/vcmi/util/FileUtil.java

@@ -81,7 +81,7 @@ public class FileUtil
     {
         if (file == null)
         {
-            Log.e("Broken path given to fileutil");
+            Log.e("Broken path given to fileutil::ensureWriteable");
             return false;
         }
 
@@ -99,6 +99,12 @@ public class FileUtil
 
     public static boolean clearDirectory(final File dir)
     {
+        if (dir == null)
+        {
+            Log.e("Broken path given to fileutil::clearDirectory");
+            return false;
+        }
+
         for (final File f : dir.listFiles())
         {
             if (f.isDirectory() && !clearDirectory(f))

+ 20 - 11
client/CMT.cpp

@@ -38,6 +38,7 @@
 #include <vstd/StringUtils.h>
 
 #include <SDL_main.h>
+#include <SDL.h>
 
 #ifdef VCMI_ANDROID
 #include "../lib/CAndroidVMHelper.h"
@@ -260,19 +261,12 @@ int main(int argc, char * argv[])
 		if (CResourceHandler::get()->existsResource(ResourceID(filename)))
 			return true;
 
-		logGlobal->error("Error: %s was not found!", message);
-		return false;
+		handleFatalError(message, false);
 	};
 
-	if (!testFile("DATA/HELP.TXT", "Heroes III data") ||
-		!testFile("MODS/VCMI/MOD.JSON", "VCMI data"))
-	{
-		exit(1); // These are unrecoverable errors
-	}
-
-	// these two are optional + some installs have them on CD and not in data directory
-	testFile("VIDEO/GOOD1A.SMK", "campaign movies");
-	testFile("SOUNDS/G1A.WAV", "campaign music"); //technically not a music but voiced intro sounds
+	testFile("DATA/HELP.TXT", "VCMI requires Heroes III: Shadow of Death or Heroes III: Complete data files to run!");
+	testFile("MODS/VCMI/MOD.JSON", "VCMI installation is corrupted! Built-in mod was not found!");
+	testFile("DATA/TENTCOLR.TXT", "Heroes III: Restoration of Erathia (including HD Edition) data files are not supported!");
 
 	srand ( (unsigned int)time(nullptr) );
 
@@ -510,3 +504,18 @@ void handleQuit(bool ask)
 		quitApplication();
 	}
 }
+
+void handleFatalError(const std::string & message, bool terminate)
+{
+	logGlobal->error("FATAL ERROR ENCOUTERED, VCMI WILL NOW TERMINATE");
+	logGlobal->error("Reason: %s", message);
+
+	std::string messageToShow = "Fatal error! " + message;
+
+	SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error!", messageToShow.c_str(), nullptr);
+
+	if (terminate)
+		throw std::runtime_error(message);
+	else
+		exit(1);
+}

+ 4 - 0
client/CMT.h

@@ -21,3 +21,7 @@ extern SDL_Surface *screen2;     // and hlp surface (used to store not-active in
 extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
 
 void handleQuit(bool ask = true);
+
+/// Notify user about encoutered fatal error and terminate the game
+/// TODO: decide on better location for this method
+[[noreturn]] void handleFatalError(const std::string & message, bool terminate);

+ 11 - 2
client/CServerHandler.cpp

@@ -572,7 +572,16 @@ void CServerHandler::sendRestartGame() const
 
 void CServerHandler::sendStartGame(bool allowOnlyAI) const
 {
-	verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
+	try
+	{
+		verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
+	}
+	catch (const std::exception & e)
+	{
+		showServerError( std::string("Unable to start map! Reason: ") + e.what());
+		return;
+	}
+
 	LobbyStartGame lsg;
 	if(client)
 	{
@@ -696,7 +705,7 @@ void CServerHandler::startCampaignScenario(std::shared_ptr<CampaignState> cs)
 	});
 }
 
-void CServerHandler::showServerError(std::string txt)
+void CServerHandler::showServerError(std::string txt) const
 {
 	CInfoWindow::showInfoDialog(txt, {});
 }

+ 1 - 1
client/CServerHandler.h

@@ -151,7 +151,7 @@ public:
 	void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr);
 	void endGameplay(bool closeConnection = true, bool restart = false);
 	void startCampaignScenario(std::shared_ptr<CampaignState> cs = {});
-	void showServerError(std::string txt);
+	void showServerError(std::string txt) const;
 
 	// TODO: LobbyState must be updated within game so we should always know how many player interfaces our client handle
 	int howManyPlayerInterfaces();

+ 8 - 0
client/mapView/MapRenderer.cpp

@@ -523,6 +523,14 @@ uint8_t MapRendererObjects::checksum(IMapRendererContext & context, const int3 &
 	for(const auto & objectID : context.getObjects(coordinates))
 	{
 		const auto * objectInstance = context.getObject(objectID);
+
+		assert(objectInstance);
+		if(!objectInstance)
+		{
+			logGlobal->error("Stray map object that isn't fading");
+			continue;
+		}
+
 		size_t groupIndex = context.objectGroupIndex(objectInstance->id);
 		Point offsetPixels = context.objectImageOffset(objectInstance->id, coordinates);
 

+ 0 - 13
client/render/Graphics.cpp

@@ -126,24 +126,11 @@ void Graphics::initializeBattleGraphics()
 }
 Graphics::Graphics()
 {
-	#if 0
-
-	std::vector<Task> tasks; //preparing list of graphics to load
-	tasks += std::bind(&Graphics::loadFonts,this);
-	tasks += std::bind(&Graphics::loadPaletteAndColors,this);
-	tasks += std::bind(&Graphics::initializeBattleGraphics,this);
-	tasks += std::bind(&Graphics::loadErmuToPicture,this);
-	tasks += std::bind(&Graphics::initializeImageLists,this);
-
-	CThreadHelper th(&tasks,std::max((ui32)1,boost::thread::hardware_concurrency()));
-	th.run();
-	#else
 	loadFonts();
 	loadPaletteAndColors();
 	initializeBattleGraphics();
 	loadErmuToPicture();
 	initializeImageLists();
-	#endif
 
 	//(!) do not load any CAnimation here
 }

+ 6 - 0
client/renderSDL/CBitmapFont.cpp

@@ -26,6 +26,12 @@
 
 void CBitmapFont::loadModFont(const std::string & modName, const ResourceID & resource)
 {
+	if (!CResourceHandler::get(modName)->existsResource(resource))
+	{
+		logGlobal->error("Failed to load font %s from mod %s", resource.getName(), modName);
+		return;
+	}
+
 	auto data = CResourceHandler::get(modName)->load(resource)->readAll();
 	std::string modLanguage = CGI->modh->getModLanguage(modName);
 	std::string modEncoding = Languages::getLanguageOptions(modLanguage).encoding;

+ 11 - 0
client/renderSDL/SDL_Extensions.cpp

@@ -80,6 +80,17 @@ SDL_Surface * CSDL_Ext::newSurface(int w, int h)
 SDL_Surface * CSDL_Ext::newSurface(int w, int h, SDL_Surface * mod) //creates new surface, with flags/format same as in surface given
 {
 	SDL_Surface * ret = SDL_CreateRGBSurface(0,w,h,mod->format->BitsPerPixel,mod->format->Rmask,mod->format->Gmask,mod->format->Bmask,mod->format->Amask);
+
+	if(ret == nullptr)
+	{
+		const char * error = SDL_GetError();
+
+		std::string messagePattern = "Failed to create SDL Surface of size %d x %d, %d bpp. Reason: %s";
+		std::string message = boost::str(boost::format(messagePattern) % w % h % mod->format->BitsPerPixel % error);
+
+		handleFatalError(message, true);
+	}
+
 	if (mod->format->palette)
 	{
 		assert(ret->format->palette);

+ 9 - 1
client/renderSDL/ScreenHandler.cpp

@@ -264,7 +264,15 @@ void ScreenHandler::initializeWindow()
 	mainWindow = createWindow();
 
 	if(mainWindow == nullptr)
-		throw std::runtime_error("Unable to create window\n");
+	{
+		const char * error = SDL_GetError();
+		Point dimensions = getPreferredWindowResolution();
+
+		std::string messagePattern = "Failed to create SDL Window of size %d x %d. Reason: %s";
+		std::string message = boost::str(boost::format(messagePattern) % dimensions.x % dimensions.y % error);
+
+		handleFatalError(message, true);
+	}
 
 	//create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible
 	mainRenderer = SDL_CreateRenderer(mainWindow, getPreferredRenderingDriver(), 0);

+ 7 - 1
client/windows/CCastleInterface.cpp

@@ -849,7 +849,13 @@ void CCastleBuildings::enterCastleGate()
 
 void CCastleBuildings::enterDwelling(int level)
 {
-	assert(level >= 0 && level < town->creatures.size());
+	if (level < 0 || level >= town->creatures.size() || town->creatures[level].second.empty())
+	{
+		assert(0);
+		logGlobal->error("Attempt to enter into invalid dwelling of level %d in town %s (%s)", level, town->getNameTranslated(), town->town->faction->getNameTranslated());
+		return;
+	}
+
 	auto recruitCb = [=](CreatureID id, int count)
 	{
 		LOCPLINT->cb->recruitCreatures(town, town->getUpperArmy(), id, count, level);

+ 12 - 4
lib/CTownHandler.cpp

@@ -1183,11 +1183,19 @@ void CTownHandler::initializeRequirements()
 		{
 			if (node.Vector().size() > 1)
 			{
-				logMod->warn("Unexpected length of town buildings requirements: %d", node.Vector().size());
-				logMod->warn("Entry contains: ");
-				logMod->warn(node.toJson());
+				logMod->error("Unexpected length of town buildings requirements: %d", node.Vector().size());
+				logMod->error("Entry contains: ");
+				logMod->error(node.toJson());
 			}
-			return BuildingID(VLC->modh->identifiers.getIdentifier(requirement.town->getBuildingScope(), node.Vector()[0]).value());
+
+			auto index = VLC->modh->identifiers.getIdentifier(requirement.town->getBuildingScope(), node[0]);
+
+			if (!index.has_value())
+			{
+				logMod->error("Unknown building in town buildings: %s", node[0].String());
+				return BuildingID::NONE;
+			}
+			return BuildingID(index.value());
 		});
 	}
 	requirementsToLoad.clear();

+ 3 - 3
lib/StartInfo.cpp

@@ -71,7 +71,7 @@ std::string StartInfo::getCampaignName() const
 void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const
 {
 	if(!mi || !mi->mapHeader)
-		throw std::domain_error("ExceptionMapMissing");
+		throw std::domain_error("There is no map to start!");
 	
 	auto missingMods = CMapService::verifyMapHeaderMods(*mi->mapHeader);
 	CModHandler::Incompatibility::ModList modList;
@@ -88,12 +88,12 @@ void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const
 			break;
 
 	if(i == si->playerInfos.cend() && !ignoreNoHuman)
-		throw std::domain_error("ExceptionNoHuman");
+		throw std::domain_error("There is no human player on map");
 
 	if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME)
 	{
 		if(!si->mapGenOptions->checkOptions())
-			throw std::domain_error("ExceptionNoTemplate");
+			throw std::domain_error("No random map template found!");
 	}
 }