If you want to help us maintaining this wiki, check out our discord server: https://discord.gg/3u69jMa 

Republic Commando UCC

From SWRC Wiki
Revision as of 11:23, 23 November 2017 by Leon (talk | contribs)
Jump to navigation Jump to search

Author: Leon

Used Tools: MS Visual Studio 2003

Description: A self written UCC for Republic Commando. Used for executing Unreal Commandlets.

Github: here

Latest Build: here


UCC.cpp

/*
*	This code compiles to a dll. In order to use it, SWRepublicCommando.exe needs to be modified so
*	that it calls this dll's 'uccInit' instead of 'appInit' from Core.dll
*	Also it's subsystem should be changed from window to console
*	Everything compiles fine with Visual Studio .NET 2003 for maximum compatibility since it was
*	also used to compile Republic Commando
*	The following settings are needed to compile everything without errors:
*		- Character Set = Not Set
*		- Struct Member Alignment = 4 Bytes
*		- Calling Convention = __fastcall
*/

#pragma comment(lib, "Core.lib")

#include <windows.h>	//VirtualProtect

#include <cstdio>	//std::puts
#include <cstdlib>	//__argc, __argv
#include <cctype>	//std::toupper
#include <string>

#include "Core/inc/Core.h"

namespace{
	//Global variables

	FOutputDevice* GFileLog;	//OutputDevice that writes to a log file

	//All commandlets shipped with RC
	const char* DefaultCommandlets[40][2] = {
		{"Editor", "AnalyzeContent"},
		{"Editor", "BatchExport"},
		{"Editor", "ChecksumPackage"},
		{"Editor", "CheckUnicode"},
		{"Editor", "CompareInt"},
		{"IpDrv", "Compress"},
		{"Editor", "CompressToDXT"},
		{"Editor", "Conform"},
		{"Editor", "ConvertMaterial"},
		{"Editor", "CutdownContent"},
		{"Editor", "DataRip"},
		{"IpDrv", "Decompress"},
		{"XGame", "DumpGameList"},
		{"Editor", "DumpInt"},
		{"XGame", "DumpMapList"},
		{"XGame", "DumpMutatorList"},
		{"Editor", "DumpSoundParams"},
		{"Editor", "DumpSoundPropLog"},
		{"XGame", "DumpWeaponList"},
		{"Editor", "DXTConvert"},
		{"Editor", "Exec"},
		{"Editor", "FontUpdate"},
		{"Core", "HelloWorld"},
		{"Editor", "ImportAse"},
		{"Editor", "ImportTexture"},
		{"Editor", "ListPackageContents"},
		{"Editor", "Make"},
		{"Editor", "MapConvert"},
		{"Editor", "Master"},
		{"Editor", "MergeInt"},
		{"Editor", "ModifyPackageFlags"},
		{"Editor", "PackageFlag"},
		{"Editor", "Pkg"},
		{"Editor", "RearrangeInt"},
		{"Editor", "ResavePackage"},
		{"Engine", "Server"},
		{"Editor", "StripSource"},
		{"Editor", "TestMath"},
		{"Editor", "UpdateUMod"},
		{"Editor", "XACTExport"}
	};

	//Helper function used for case-insensitive string comparisons
	std::string ToUpper(const std::string& s){
		std::string result;

		result.reserve(s.size());

		for(std::size_t i = 0; i < s.size(); i++)
			result += std::toupper(s[i]);

		return result;
	}

	/*
	*	Replacement for Serialize function of 'InWarn', passed to appInit
	*	Have to do this vtable hack because creating new FFeedbackContext and passing it results in crash
	*/
	void __stdcall FFeedbackContextAnsiSerialize(const TCHAR* V, EName Event){
		TCHAR Temp[1024]= "";

		if(Event==NAME_Title){
			return;
		}else if(Event==NAME_Heading){
			appSprintf(Temp, "\n--------------------%s--------------------", V);

			V = Temp;
		}else if(Event==NAME_SubHeading){
			appSprintf(Temp, "%s...", V);

			V = Temp;
		}else if(Event == NAME_Error || Event == NAME_Warning || Event == NAME_ExecWarning || Event == NAME_ScriptWarning || Event == NAME_Critical){
			appSprintf(Temp, "%s: %s", *FName(Event), V);

			V = Temp;
		}

		std::puts(V);

		GFileLog->Serialize(V, Event);
	}
}

//Entry point, called by modified SWRepublicCommando.exe
__declspec(dllexport) void uccInit(const TCHAR* InPackage, const TCHAR* InCmdLine, FOutputDevice* InLog, FOutputDeviceError* InError,
								   FFeedbackContext* InWarn, FConfigCache*(*ConfigFactory)(), UBOOL RequireConfig){
	int ExitVal = EXIT_SUCCESS;
	
	GFileLog = InLog;

	//Small vtable hack to have InWarn call my own Serialize function
	//===============================================================
	PVOID* vtable = *reinterpret_cast<PVOID**>(InWarn);
	DWORD dwNull;

	VirtualProtect(&vtable[0], 4, PAGE_EXECUTE_READWRITE, &dwNull);

	vtable[0] = FFeedbackContextAnsiSerialize;
	//===============================================================

	InWarn->Log("=======================================");
	InWarn->Log("ucc.exe for Star Wars Republic Commando");
	InWarn->Log("made by Leon0628");
	InWarn->Log("=======================================\n");

	//Server stuff...

	for(int i = 1; i < __argc; i++){
		std::string Temp = ToUpper(__argv[i]);

		//In case user wants to start a server, redirecting log to console and returning to SWRepublicCommando.exe
		//Uses RC server command line syntax, not Engine.ServerCommandlet!!!
		if(Temp == "-SERVER"){
			appInit(InPackage, InCmdLine, InWarn, InError, InWarn, ConfigFactory, RequireConfig);

			return;
		}
	}

	//Actual UCC stuff...

	try{
		GIsStarted = 1;
		GIsGuarded = 1;

		appInit(InPackage, InCmdLine, InLog, InError, InWarn, ConfigFactory, 1);
		UObject::SetLanguage("int");

		if(__argc > 1){
			//Initializing global state
			GIsUCC = GIsClient = GIsServer = GIsEditor = GIsScriptable = GLazyLoad = 1;

			std::string ClassName = __argv[1];
			std::string Upper = ToUpper(ClassName);
			DWORD LoadFlags = LOAD_NoWarn | LOAD_Quiet;
			bool bIsUScript = true;	//Whether the commandlet is written in UnrealScript or native code

			//Looking it up in list of default commandlets
			for(int i = 0; i < ARRAY_COUNT(DefaultCommandlets); i++){
				if(Upper == ToUpper(DefaultCommandlets[i][1]) ||	//Only name without package
				   Upper == ToUpper(DefaultCommandlets[i][1]) + "COMMANDLET"){	//Only name with "Commandlet"
					ClassName = std::string(DefaultCommandlets[i][0]) + "." + DefaultCommandlets[i][1] + "Commandlet";

					//HelloWorld is the only standard commandlet written in UnrealScript
					bIsUScript = ClassName == "Core.HelloWorldCommandlet";

					break;
				}else if(Upper == ToUpper(std::string(DefaultCommandlets[i][0]) + "." + DefaultCommandlets[i][1]) ||	//Package.Name
						 Upper == ToUpper(std::string(DefaultCommandlets[i][0]) + "." + DefaultCommandlets[i][1]) + "COMMANDLET"){	//Package.Name + "Commandlet"
					//HelloWorld is the only standard commandlet written in UnrealScript
					bIsUScript = Upper == "CORE.HELLOWORLD" || Upper == "CORE.HELLOWORLDCOMMANDLET";

					break;
				}
			}

			if(ToUpper(ClassName) == "EDITOR.MAKE" || ToUpper(ClassName) == "EDITOR.MAKECOMMANDLET")
				LoadFlags |= LOAD_DisallowFiles;	//Not sure what this does but the original ucc has it aswell...

			UClass* Class = UObject::StaticLoadClass(UCommandlet::StaticClass(), NULL, ClassName.c_str(), NULL, LoadFlags, NULL);

			if(!Class)	//If class failed to load, appending "Commandlet" and trying again
				Class = UObject::StaticLoadClass(UCommandlet::StaticClass(), NULL, (ClassName + "Commandlet").c_str(), NULL, LoadFlags, NULL);

			if(Class){
				UCommandlet* Commandlet = ConstructObject<UCommandlet>(Class);

				InWarn->Logf("Executing %s\n", Class->GetFullName());

				Commandlet->InitExecution();
				Commandlet->ParseParms(appCmdLine());

				GLog = InWarn;	//Redirecting commandlet output to console

				if(!bIsUScript)
					Commandlet->Main(appCmdLine());
				else
					Commandlet->Main(FString(appCmdLine()));	//For non-native commandlets this overload has to be used

				GLog = GFileLog;
			}else{
				InWarn->Logf("Commandlet %s not found", ClassName.c_str());
			}
		}else{
			InWarn->Log("Usage:");
			InWarn->Log("    ucc PackageName.CommandletName <parameters>");
			InWarn->Log("OR");
			InWarn->Log("    ucc uscript PackageName.CommandletName <parameters>");
			InWarn->Log("    for commandlets written in UnrealScript");
		}

		appPreExit();
		GIsGuarded = 0;
	}catch(...){
		ExitVal = EXIT_FAILURE;
		GIsGuarded = 0;
		InError->HandleError();
	}

	appExit();
	std::exit(ExitVal);	//Needed in order to prevent this function from returning to SWRepublicCommando.exe
}