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
(7 intermediate revisions by the same user not shown)
Line 5: Line 5:
Description: A self written UCC for Republic Commando. Used for executing Unreal Commandlets.
Description: A self written UCC for Republic Commando. Used for executing Unreal Commandlets.


Github: [https://github.com/Leon280698/Republic-Commando-UCC here]
Github: [https://github.com/Leon280698/CT here]


Latest Build: [http://www.moddb.com/games/star-wars-republic-commando/downloads/star-wars-republic-commando-ucc-exe here]
Latest Build: [http://www.moddb.com/games/star-wars-republic-commando/downloads/star-wars-republic-commando-ucc-exe here]
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 using the UT99 headers the call to appInit always fails due to the parameters'
* types being substantially different in RC (especially FConfigCacheIni). By replacing the appInit call in
* SWRepublicCommando.exe it is possible to use the parameters that are originally passed by the game and since
* they're just pointers it's no problem to use them to call appInit from Core.dll.
* Also the exe's subsystem has to be changed from window to console for obvious reasons...
* 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
* The following settings are required in order to compile everything without errors:
* The following settings are required in order to compile everything without errors:
* - Character Set = Not Set
* - Character Set = Not Set //Important as RC does not use unicode
* - Struct Member Alignment = 4 Bytes
* - Struct Member Alignment = 4 Bytes //Probably not necessary, but just in case...
* - Calling Convention = __fastcall
* - Calling Convention = __fastcall //RC uses __fastcall as default calling convention
*/
*/


#pragma comment(lib, "Core/lib/Core.lib")
#include "../../Core/Inc/Core.h"
#pragma comment(lib, "Engine/lib/Engine.lib")
#include "../../Core/Inc/FOutputDeviceFile.h"
#include "../../Core/Inc/FOutputDeviceWindowsError.h"
#include "../../Core/Inc/FFeedbackContextAnsi.h"
#include "../../Core/Inc/FConfigCacheIni.h"


#include <Windows.h>
void UServerCommandletMain(); //Defined in ServerCommandlet.cpp


#include <cstdlib> //__argc, __argv
//Output devices
#include <cctype> //std::toupper
#include <cstdio> //puts, fgets


#include "Engine/Inc/Engine.h" //Core and Engine
FOutputDeviceFile Log;
#include "Core/Inc/FOutputDeviceWindowsError.h" //For modal error messages that are not displayed in the console
FOutputDeviceWindowsError Error;
FFeedbackContextAnsi Warn;


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


FOutputDevice* FileLog; //Used by FFeedbackContextAnsiSerialize to write to the log file because GLog might be reassigned
int main(int argc, char** argv){
int WarningCount = 0; //Incremented whenever a warning occurs in FFeedbackContextAnsiSerialize
GIsStarted = 1;
int ErrorCount = 0; //Same as WarningCount just with errors...


/*
try{
* Replacement for Serialize function of 'InWarn', passed to appInit which is an FFeedbackContextWindows
GIsGuarded = 1;
*/
void __stdcall FFeedbackContextAnsiSerialize(const TCHAR* V, EName Event){
TCHAR Buffer[1024]= "";
const TCHAR* Temp = V;


if(Event == NAME_Title){
FString CmdLine;
SetConsoleTitle(V);


return; //Prevents the server from spamming the player count to the log
for(int i = 1; i < argc; i++)
}else if(Event == NAME_Heading){
CmdLine += FString(argv[i]) + " ";
appSprintf(Buffer, "\n--------------------%s--------------------", V);


Temp = Buffer;
appInit("SWRepublicCommando", *CmdLine, &Log, &Error, &Warn, FConfigCacheIni::Factory, 1);
V = Buffer; //So that the log file also contains the formatted string
UObject::SetLanguage("int");
}else if(Event == NAME_Warning || Event == NAME_ExecWarning || Event == NAME_ScriptWarning){
appSprintf(Buffer, "%s: %s", *FName(Event), V);


WarningCount++;
if(argc > 1){
 
//Initializing global state
Temp = Buffer;
GIsUCC = GIsClient = GIsServer = GIsEditor = GIsScriptable = GLazyLoad = 1;
}else if(Event == NAME_Error || Event == NAME_Critical){
appSprintf(Buffer, "%s: %s", *FName(Event), V);


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


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


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


FileLog->Serialize(V, Event);
if(ClassName == Str || ClassName + "Commandlet" == Str){ //Checking against "PackageName.ClassName (+ Commandlet)"
}
ClassName = List[i].Object;


/*
break;
* Allows user input in the console while running a server
}
* This function runs in a separate thread, not sure if that is a good solution though...
*/
DWORD WINAPI UpdateServerConsoleInput(PVOID){
char Cmd[1024];


while(GIsRunning && !GIsRequestingExit){
while(Str.InStr(".") >= 0) //Removing package name so that only class name remains
if(std::fgets(Cmd, sizeof(Cmd), stdin))
Str = Str.Mid(Str.InStr(".") + 1);
GEngine->Exec(Cmd, *GWarn);
}


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


/*
break;
* 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
//The original one doesn't assign a value to GEngine which leads to a gpf...
GEngine = ConstructObject<UEngine>(EngineClass);
 
GEngine->Init();
 
CreateThread(NULL, 0, UpdateServerConsoleInput, NULL, 0, NULL);
 
double OldTime = appSeconds();
 
GIsRunning = 1;
 
//Main loop
while(GIsRunning && !GIsRequestingExit){
double NewTime = appSeconds();
 
//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;
}
}
/*
* Entry point, called by modified SWRepublicCommando.exe
*/
__declspec(dllexport) void uccMain(const TCHAR* InPackage, const TCHAR* InCmdLine, FOutputDevice* InLog, FOutputDeviceError* /*InError*/,
  FFeedbackContext* InWarn, FConfigCache*(*ConfigFactory)(), UBOOL /*RequireConfig*/){
FOutputDeviceWindowsError Error; //Error handling using message boxes which is just nicer
FileLog = InLog;
/*
* Replacing Serialize function in vtable of InWarn with custom one that prints to the console
* This is necessary because creating a new FFeedbackContext causes problems with 'BeginSlowTask' which
* leads to a crash in appLoadPackageFile for whatever reason...
* A nice side effect though, is that the features of FFeedbackContext Windows, like message boxes remain with the
* only change being the log output to the console
*/
{//===============================================================
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");
try{
GIsStarted = 1;
GIsGuarded = 1;
appInit(InPackage, InCmdLine, InLog, &Error, InWarn, ConfigFactory, 1);
UObject::SetLanguage("int");
if(__argc > 1){
//Initializing global state
GIsUCC = GIsClient = GIsServer = GIsEditor = GIsScriptable = GLazyLoad = 1;
FString Token = FString(__argv[1]).Caps();
DWORD LoadFlags = LOAD_NoWarn | LOAD_Quiet;
DWORD LoadFlags = LOAD_NoWarn | LOAD_Quiet;


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


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


if(!Class) //If class failed to load, appending "Commandlet" and trying again
if(!Class) //If class failed to load appending "Commandlet" and trying again
Class = LoadClass<UCommandlet>(NULL, *(Token + "Commandlet"), NULL, LoadFlags, NULL);
Class = LoadClass<UCommandlet>(NULL, *(ClassName + "Commandlet"), NULL, LoadFlags, NULL);
 
if(!Class){
TArray<FRegistryObjectInfo> List; //Loading list of commandlets declared in int files
UObject::GetRegistryObjects(List, UClass::StaticClass(), UCommandlet::StaticClass(), 0);
 
for(int i = 0; i < List.Num(); i++){ //Looking Token up in list and autocompleting class name if found
FString Str = List(i).Object;
 
while(Str.InStr(".") >= 0)
Str = Str.Mid(Str.InStr(".") + 1);
 
if(Token == Str || Token + "Commandlet" == Str){
Class = LoadClass<UCommandlet>(NULL, *List(i).Object, NULL, LoadFlags, NULL);
 
break;
}
}
}


if(Class){
if(Class){
Line 212: Line 100:
UCommandlet* Default = static_cast<UCommandlet*>(Class->GetDefaultObject());
UCommandlet* Default = static_cast<UCommandlet*>(Class->GetDefaultObject());


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


GIsClient = Default->IsClient;
GIsClient = Default->IsClient;
Line 219: Line 110:
GLazyLoad = Default->LazyLoad;
GLazyLoad = Default->LazyLoad;


FString CommandletCmdLine; //Contains only the command-line options that are passed to the commandlet to avoid problems with some commandlets
//Contains only the command-line options that are passed to the commandlet
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();
Commandlet->ParseParms(*CommandletCmdLine);
Commandlet->ParseParms(*CommandletCmdLine);


if(Default->LogToStdout)
if(Default->LogToStdout){ //Redirecting commandlet output to console
GLog = InWarn; //Redirecting commandlet output to console
Warn.AuxOut = GLog;
GLog = &Warn;
}
if(Token == "SERVER" || Token == "SERVERCOMMANDLET" || Token == "ENGINE.SERVER" || Token == "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(CommandletCmdLine.GetIntSizeString()); //If UnrealScript main function exists this overload needs to be used
else
else
Commandlet->Main(*CommandletCmdLine);
Commandlet->Main(CommandletCmdLine);


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


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


appPreExit();
//This prevents an infinite loop during garbage collection when there are compile errors with ucc make
//Hopefully only a temporary fix...
if(Warn.ErrorCount == 0)
appPreExit();


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


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

Revision as of 19:19, 4 August 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 "../../Core/Inc/Core.h"
#include "../../Core/Inc/FOutputDeviceFile.h"
#include "../../Core/Inc/FOutputDeviceWindowsError.h"
#include "../../Core/Inc/FFeedbackContextAnsi.h"
#include "../../Core/Inc/FConfigCacheIni.h"

void UServerCommandletMain(); //Defined in ServerCommandlet.cpp

//Output devices

FOutputDeviceFile Log;
FOutputDeviceWindowsError Error;
FFeedbackContextAnsi Warn;

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){
	GIsStarted = 1;

	try{
		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
				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>");
		}

		//This prevents an infinite loop during garbage collection when there are compile errors with ucc make
		//Hopefully only a temporary fix...
		if(Warn.ErrorCount == 0)
			appPreExit();

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

	appExit();
}