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

Difference between revisions of "Republic Commando UCC"

From SWRC Wiki
Jump to navigation Jump to search
Line 14: Line 14:
<source lang="cpp" line">
<source lang="cpp" line">
/*
/*
* This code compiles to a dll. In order to use it, SWRepublicCommando.exe needs to be modified so
* This is a custom UCC.exe for Star Wars Republic Commando since the game shipped without one.
* that it calls this dll's 'uccMain' instead of 'appInit' from Core.dll.
* This is necessary because the call to appInit always fails due to problems with FConfigCacheIni
* that have not yet been possible to solve completely. By intercepting the call to appInit in the original
* game executable it is possible to simply use the parameters that are passed by the game.
* Everything compiles fine with Visual Studio .NET 2003 which is being used to achieve maximum compatibility
* Everything compiles fine with Visual Studio .NET 2003 which is being used to achieve maximum compatibility
* since it was also used to compile RC
* since it was also used to compile RC
Line 27: Line 23:
*/
*/


#include <Windows.h>
#include <cstdio>


#include <cstdlib> //__argc, __argv
#include "../../Engine/Inc/Engine.h" //Core and Engine


#include "../../Engine/Inc/Engine.h" //Core and Engine
#include "../../Core/Inc/FOutputDeviceFile.h"
#include "../../Core/Inc/FOutputDeviceFile.h"
#include "../../Core/Inc/FOutputDeviceWindowsError.h" //For modal error messages that are not displayed in the console
FOutputDeviceFile Log;
 
#include "../../Core/Inc/FOutputDeviceWindowsError.h" //Error handling using message boxes which is just nicer than having everything in the console
FOutputDeviceWindowsError Error;
 
#include "../../Core/Inc/FFeedbackContextAnsi.h"
#include "../../Core/Inc/FFeedbackContextAnsi.h"
FFeedbackContextAnsi Warn;


namespace{
#include "../../Core/Inc/FConfigCacheIni.h"
FOutputDeviceFile Log; //Log file
FOutputDeviceWindowsError Error; //Error handling using message boxes which is just nicer than having everything in the console
FFeedbackContextAnsi Warn;


//Variables for ServerCommandlet
//Variables for ServerCommandlet


FString CurrentCmd; //Contains the next command that is to be passed to UEngine::Exec gathered by the input thread
FString CurrentCmd; //Contains the next command that is to be passed to UEngine::Exec gathered by the input thread
//the command is then executed by the main thread to avoid issues
//the command is then executed by the main thread to avoid issues


/*
/*
* Allows user input in the console while running a server
* Allows user input in the console while running a server
* This function runs in a separate thread in order to not having
* This function runs in a separate thread in order to not having
* to pause the main loop while waiting for input
* to pause the main loop while waiting for input
*/
*/
DWORD WINAPI UpdateServerConsoleInput(PVOID){
DWORD WINAPI UpdateServerConsoleInput(PVOID){
char Cmd[1024];
TCHAR Cmd[1024];


while(GIsRunning && !GIsRequestingExit){
while(GIsRunning && !GIsRequestingExit){
if(std::fgets(Cmd, sizeof(Cmd), stdin)){
if(std::fgets(Cmd, sizeof(Cmd), stdin)){
Cmd[appStrlen(Cmd) - 1] = '\0'; //Removing newline added by fgets
Cmd[appStrlen(Cmd) - 1] = '\0'; //Removing newline added by fgets
CurrentCmd = Cmd; //Updating CurrentCmd so that it can be executed by the main thread
CurrentCmd = Cmd; //Updating CurrentCmd so that it can be executed by the main thread
  //Nothing has been done in terms of thread safety as so far there haven't been any issues...
  //Nothing has been done in terms of thread safety as so far there haven't been any issues...


//Returning in case user requested exit to not get to fgets again
//Returning in case user requested exit in order to not get to fgets again
if(CurrentCmd.Caps() == "EXIT" || CurrentCmd.Caps() == "QUIT")
if(CurrentCmd == "EXIT" || CurrentCmd == "QUIT")
return 0;
return 0;
}
}
}
}
return 0;
}
/*
* Replacement for UServerCommandlet::Main since the one from Engine.dll crashes because it doesn't assign a value to GEngine
*/
void UServerCommandletMain(){
FString Language;


return 0;
if(GConfig->GetString("Engine.Engine", "Language", Language, "System.ini"))
}
UObject::SetLanguage(*Language);


/*
UClass* EngineClass = LoadClass<UEngine>(NULL, "ini:Engine.Engine.GameEngine", NULL, LOAD_NoFail, NULL);
* Replacement for UServerCommandlet::Main since the one from Engine.dll crashes because it doesn't assign a value to GEngine
*/
void UServerCommandletMain(){
UClass* EngineClass = LoadClass<UEngine>(NULL, "Engine.GameEngine", NULL, LOAD_NoFail, NULL);


//Literally the only reason for this function to be rewritten
//Literally the only reason for this function to be rewritten
//The original one doesn't assign a value to GEngine which leads to a gpf...
//The original one doesn't assign a value to GEngine which leads to a gpf...
GEngine = ConstructObject<UEngine>(EngineClass);
GEngine = ConstructObject<UEngine>(EngineClass);


GEngine->Init();
GEngine->Init();


//Creating input thread
//Creating input thread
CreateThread(NULL, 0, UpdateServerConsoleInput, NULL, 0, NULL);
CreateThread(NULL, 0, UpdateServerConsoleInput, NULL, 0, NULL);


double OldTime = appSeconds();
double OldTime = appSeconds();


GIsRunning = 1;
GIsRunning = 1;


//Main loop
//Main loop
while(GIsRunning && !GIsRequestingExit){
while(GIsRunning && !GIsRequestingExit){
double NewTime = appSeconds();
double NewTime = appSeconds();


//Executing console commands that are gathered by UpdateServerConsoleInput in a different thread
//Executing console commands that are gathered by UpdateServerConsoleInput in a different thread
if(CurrentCmd.Num() > 0){
if(CurrentCmd.Len() > 0){
GEngine->Exec(*CurrentCmd, *GWarn);
GEngine->Exec(*CurrentCmd, Warn);
CurrentCmd.Empty();
CurrentCmd.Empty();
}
}


//Update the world
//Update the world
GEngine->Tick(NewTime - OldTime);
GEngine->Tick(NewTime - OldTime);
OldTime = NewTime;
OldTime = NewTime;


//Enforce optional maximum tick rate
//Enforce optional maximum tick rate
float MaxTickRate = GEngine->GetMaxTickRate();
float MaxTickRate = GEngine->GetMaxTickRate();


if(MaxTickRate > 0.0f){
if(MaxTickRate > 0.0f){
float Delta = (1.0f / MaxTickRate) - (appSeconds() - OldTime);
float Delta = (1.0f / MaxTickRate) - (appSeconds() - OldTime);


appSleep(Delta > 0.0f ? Delta : 0.0f);
appSleep(Delta > 0.0f ? Delta : 0.0f);
}
}
}
}


GIsRunning = 0;
GIsRunning = 0;
}
}


void ShowBanner(){
void ShowBanner(){
GWarn->Log("=======================================");
Warn.Log("=======================================");
GWarn->Log("ucc.exe for Star Wars Republic Commando");
Warn.Log("ucc.exe for Star Wars Republic Commando");
GWarn->Log("made by Leon0628");
Warn.Log("made by Leon0628");
GWarn->Log("=======================================\n");
Warn.Log("=======================================\n");
}
}
}


/*
 
* Entry point, called by modified SWRepublicCommando.exe
int main(int argc, char** argv){
*/
__declspec(dllexport) void uccMain(const TCHAR* InPackage, const TCHAR* InCmdLine, FOutputDevice* /*InLog*/, FOutputDeviceError* /*InError*/,
  FFeedbackContext* /*InWarn*/, FConfigCache*(*ConfigFactory)(), UBOOL /*RequireConfig*/){
try{
try{
GIsStarted = 1;
GIsStarted = 1;
GIsGuarded = 1;
GIsGuarded = 1;


appInit(InPackage, InCmdLine, &Log, &Error, &Warn, ConfigFactory, 1);
FString CmdLine;
 
for(int i = 1; i < argc; i++)
CmdLine += FString(argv[i]) + " ";
 
appInit("SWRepublicCommando", *CmdLine, &Log, &Error, &Warn, FConfigCacheIni::Factory, 1);
UObject::SetLanguage("int");
UObject::SetLanguage("int");


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


FString ClassName = __argv[1];
FString ClassName = argv[1];
TArray<FRegistryObjectInfo> List;
TArray<FRegistryObjectInfo> List;


Line 165: Line 168:
DWORD LoadFlags = LOAD_NoWarn | LOAD_Quiet;
DWORD LoadFlags = LOAD_NoWarn | LOAD_Quiet;


if(ClassName == "Make")
if(ClassName == "Editor.MakeCommandlet")
LoadFlags |= LOAD_DisallowFiles; //Not entirely sure what this does but the original ucc has it as well...
LoadFlags |= LOAD_DisallowFiles;


UClass* Class = LoadClass<UCommandlet>(NULL, *ClassName, NULL, LoadFlags, NULL);
UClass* Class = LoadClass<UCommandlet>(NULL, *ClassName, NULL, LoadFlags, NULL);
Line 190: Line 193:
FString CommandletCmdLine;
FString CommandletCmdLine;


for(int i = 2; i < __argc; i++)
for(int i = 2; i < argc; i++)
CommandletCmdLine += FString(__argv[i]) + " ";
CommandletCmdLine += FString(argv[i]) + " ";


Commandlet->InitExecution();
Commandlet->InitExecution();
Line 203: Line 206:
if(ClassName == "Engine.ServerCommandlet")
if(ClassName == "Engine.ServerCommandlet")
UServerCommandletMain(); //The ServerCommandlet has a special Main function
UServerCommandletMain(); //The ServerCommandlet has a special Main function
else if(Commandlet->FindFunction(NAME_Main))
Commandlet->Main(GetIntSizeString(CommandletCmdLine)); //If UnrealScript main function is found this overload has to be used
else
else
Commandlet->Main(*CommandletCmdLine);
Commandlet->Main(CommandletCmdLine);


if(Default->ShowErrorCount)
if(Default->ShowErrorCount)
Line 217: Line 218:
}else{
}else{
ShowBanner();
ShowBanner();
Warn.Logf("Commandlet %s not found", __argv[1]);
Warn.Logf("Commandlet %s not found", argv[1]);
}
}
}else{
}else{
Line 230: Line 231:
}catch(...){
}catch(...){
GIsGuarded = 0;
GIsGuarded = 0;
GLog = &Log;
Error.HandleError();
Error.HandleError();
}
}


appExit();
appExit();
appRequestExit(1); //Needed in order to prevent this function from returning to SWRepublicCommando.exe
}
}
</source>
</source>

Revision as of 22:22, 29 May 2018

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 is a custom UCC.exe for Star Wars Republic Commando since the game shipped without one.
*	Everything compiles fine with Visual Studio .NET 2003 which is being used to achieve maximum compatibility
*	since it was also used to compile RC
*	The following settings are required in order to compile everything without errors:
*		- Character Set = Not Set	//Important as RC does not use unicode
*		- Struct Member Alignment = 4 Bytes	//Probably not necessary, but just in case...
*		- Calling Convention = __fastcall	//RC uses __fastcall as default calling convention
*/

#include <cstdio>

#include "../../Engine/Inc/Engine.h" //Core and Engine

#include "../../Core/Inc/FOutputDeviceFile.h"
FOutputDeviceFile Log;

#include "../../Core/Inc/FOutputDeviceWindowsError.h" //Error handling using message boxes which is just nicer than having everything in the console
FOutputDeviceWindowsError Error;

#include "../../Core/Inc/FFeedbackContextAnsi.h"
FFeedbackContextAnsi Warn;

#include "../../Core/Inc/FConfigCacheIni.h"

//Variables for ServerCommandlet

FString CurrentCmd; //Contains the next command that is to be passed to UEngine::Exec gathered by the input thread
					//the command is then executed by the main thread to avoid issues

/*
*	Allows user input in the console while running a server
*	This function runs in a separate thread in order to not having
*	to pause the main loop while waiting for input
*/
DWORD WINAPI UpdateServerConsoleInput(PVOID){
	TCHAR Cmd[1024];

	while(GIsRunning && !GIsRequestingExit){
		if(std::fgets(Cmd, sizeof(Cmd), stdin)){
			Cmd[appStrlen(Cmd) - 1] = '\0'; //Removing newline added by fgets
			CurrentCmd = Cmd; //Updating CurrentCmd so that it can be executed by the main thread
							  //Nothing has been done in terms of thread safety as so far there haven't been any issues...

			//Returning in case user requested exit in order to not get to fgets again
			if(CurrentCmd == "EXIT" || CurrentCmd == "QUIT")
				return 0;
		}
	}

	return 0;
}

/*
*	Replacement for UServerCommandlet::Main since the one from Engine.dll crashes because it doesn't assign a value to GEngine
*/
void UServerCommandletMain(){
	FString Language;

	if(GConfig->GetString("Engine.Engine", "Language", Language, "System.ini"))
		UObject::SetLanguage(*Language);

	UClass* EngineClass = LoadClass<UEngine>(NULL, "ini:Engine.Engine.GameEngine", NULL, LOAD_NoFail, NULL);

	//Literally the only reason for this function to be rewritten
	//The original one doesn't assign a value to GEngine which leads to a gpf...
	GEngine = ConstructObject<UEngine>(EngineClass);

	GEngine->Init();

	//Creating input thread
	CreateThread(NULL, 0, UpdateServerConsoleInput, NULL, 0, NULL);

	double OldTime = appSeconds();

	GIsRunning = 1;

	//Main loop
	while(GIsRunning && !GIsRequestingExit){
		double NewTime = appSeconds();

		//Executing console commands that are gathered by UpdateServerConsoleInput in a different thread
		if(CurrentCmd.Len() > 0){
			GEngine->Exec(*CurrentCmd, Warn);
			CurrentCmd.Empty();
		}

		//Update the world
		GEngine->Tick(NewTime - OldTime);
		OldTime = NewTime;

		//Enforce optional maximum tick rate
		float MaxTickRate = GEngine->GetMaxTickRate();

		if(MaxTickRate > 0.0f){
			float Delta = (1.0f / MaxTickRate) - (appSeconds() - OldTime);

			appSleep(Delta > 0.0f ? Delta : 0.0f);
		}
	}

	GIsRunning = 0;
}

void ShowBanner(){
	Warn.Log("=======================================");
	Warn.Log("ucc.exe for Star Wars Republic Commando");
	Warn.Log("made by Leon0628");
	Warn.Log("=======================================\n");
}


int main(int argc, char** argv){
	try{
		GIsStarted = 1;
		GIsGuarded = 1;

		FString CmdLine;

		for(int i = 1; i < argc; i++)
			CmdLine += FString(argv[i]) + " ";

		appInit("SWRepublicCommando", *CmdLine, &Log, &Error, &Warn, FConfigCacheIni::Factory, 1);
		UObject::SetLanguage("int");

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

			FString ClassName = argv[1];
			TArray<FRegistryObjectInfo> List;

			UObject::GetRegistryObjects(List, UClass::StaticClass(), UCommandlet::StaticClass(), 0); //Loading list of commandlets declared in .int files

			for(int i = 0; i < List.Num(); i++){ //Looking Token up in list and autocompleting class name if found
				FString Str = List[i].Object;

				if(ClassName == Str || ClassName + "Commandlet" == Str){ //Checking against "PackageName.ClassName (+ Commandlet)"
					ClassName = List[i].Object;

					break;
				}

				while(Str.InStr(".") >= 0) //Removing package name so that only class name remains
					Str = Str.Mid(Str.InStr(".") + 1);

				if(ClassName == Str || ClassName + "Commandlet" == Str){ //Checking against "ClassName (+ Commandlet)" and adding "PackageName"
					ClassName = List[i].Object;

					break;
				}
			}

			DWORD LoadFlags = LOAD_NoWarn | LOAD_Quiet;

			if(ClassName == "Editor.MakeCommandlet")
				LoadFlags |= LOAD_DisallowFiles;

			UClass* Class = LoadClass<UCommandlet>(NULL, *ClassName, NULL, LoadFlags, NULL);

			if(!Class) //If class failed to load appending "Commandlet" and trying again
				Class = LoadClass<UCommandlet>(NULL, *(ClassName + "Commandlet"), NULL, LoadFlags, NULL);

			if(Class){
				UCommandlet* Commandlet = ConstructObject<UCommandlet>(Class);
				UCommandlet* Default = static_cast<UCommandlet*>(Class->GetDefaultObject());

				if(Default->ShowBanner)
					ShowBanner();

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

				GIsClient = Default->IsClient;
				GIsEditor = Default->IsEditor;
				GIsServer = Default->IsServer;
				GLazyLoad = Default->LazyLoad;

				//Contains only the command-line options that are passed to the commandlet to avoid problems with some commandlets
				FString CommandletCmdLine;

				for(int i = 2; i < argc; i++)
					CommandletCmdLine += FString(argv[i]) + " ";

				Commandlet->InitExecution();
				Commandlet->ParseParms(*CommandletCmdLine);

				if(Default->LogToStdout){ //Redirecting commandlet output to console
					Warn.AuxOut = GLog;
					GLog = &Warn;
				}
				
				if(ClassName == "Engine.ServerCommandlet")
					UServerCommandletMain(); //The ServerCommandlet has a special Main function
				else
					Commandlet->Main(CommandletCmdLine);

				if(Default->ShowErrorCount)
					Warn.Logf("\n%s - %i error(s), %i warning(s)", Warn.ErrorCount == 0 ? "Success" : "Failure", Warn.ErrorCount, Warn.WarningCount);

				if(Default->LogToStdout){
					Warn.AuxOut = NULL;
					GLog = &Log;
				}
			}else{
				ShowBanner();
				Warn.Logf("Commandlet %s not found", argv[1]);
			}
		}else{
			ShowBanner();
			Warn.Log("Usage:");
			Warn.Log("    ucc CommandletName <parameters>");
		}

		appPreExit();

		GIsGuarded = 0;
	}catch(...){
		GIsGuarded = 0;
		GLog = &Log;
		Error.HandleError();
	}

	appExit();
}