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"
Jump to navigation
Jump to search
Line 30: | Line 30: | ||
#include <cstdlib> //__argc, __argv | #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/FOutputDeviceWindowsError.h" //For modal error messages that are not displayed in the console | #include "../../Core/Inc/FOutputDeviceWindowsError.h" //For modal error messages that are not displayed in the console | ||
#include "../../Core/Inc/FFeedbackContextAnsi.h" | |||
namespace{ | namespace{ | ||
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 | ||
Line 46: | Line 45: | ||
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 | ||
/* | /* | ||
Line 161: | Line 126: | ||
* Entry point, called by modified SWRepublicCommando.exe | * Entry point, called by modified SWRepublicCommando.exe | ||
*/ | */ | ||
__declspec(dllexport) void uccMain(const TCHAR* InPackage, const TCHAR* InCmdLine, FOutputDevice* InLog, FOutputDeviceError* /*InError*/, | __declspec(dllexport) void uccMain(const TCHAR* InPackage, const TCHAR* InCmdLine, FOutputDevice* /*InLog*/, FOutputDeviceError* /*InError*/, | ||
FFeedbackContext* InWarn, FConfigCache*(*ConfigFactory)(), UBOOL /*RequireConfig*/){ | FFeedbackContext* /*InWarn*/, FConfigCache*(*ConfigFactory)(), UBOOL /*RequireConfig*/){ | ||
try{ | try{ | ||
GIsStarted = 1; | GIsStarted = 1; | ||
GIsGuarded = 1; | GIsGuarded = 1; | ||
appInit(InPackage, InCmdLine, | appInit(InPackage, InCmdLine, &Log, &Error, &Warn, ConfigFactory, 1); | ||
UObject::SetLanguage("int"); | UObject::SetLanguage("int"); | ||
Line 195: | Line 139: | ||
GIsUCC = GIsClient = GIsServer = GIsEditor = GIsScriptable = GLazyLoad = 1; | GIsUCC = GIsClient = GIsServer = GIsEditor = GIsScriptable = GLazyLoad = 1; | ||
FString | FString ClassName = __argv[1]; | ||
TArray<FRegistryObjectInfo> List; | |||
UClass | 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 == "Make") | |||
LoadFlags |= LOAD_DisallowFiles; //Not entirely sure what this does but the original ucc has it as well... | |||
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){ | if(Class){ | ||
Line 231: | Line 180: | ||
ShowBanner(); | ShowBanner(); | ||
Warn.Logf("Executing %s\n", Class->GetFullName()); | |||
GIsClient = Default->IsClient; | GIsClient = Default->IsClient; | ||
Line 247: | Line 196: | ||
Commandlet->ParseParms(*CommandletCmdLine); | Commandlet->ParseParms(*CommandletCmdLine); | ||
if(Default->LogToStdout) | if(Default->LogToStdout){ //Redirecting commandlet output to console | ||
Warn.AuxOut = GLog; | |||
GLog = &Warn; | |||
} | |||
if( | 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)) | else if(Commandlet->FindFunction(NAME_Main)) | ||
Line 258: | Line 209: | ||
if(Default->ShowErrorCount) | if(Default->ShowErrorCount) | ||
Warn.Logf("\n%s - %i error(s), %i warning(s)", Warn.ErrorCount == 0 ? "Success" : "Failure", Warn.ErrorCount, Warn.WarningCount); | |||
GLog = | if(Default->LogToStdout){ | ||
Warn.AuxOut = NULL; | |||
GLog = &Log; | |||
} | |||
}else{ | }else{ | ||
ShowBanner(); | ShowBanner(); | ||
Warn.Logf("Commandlet %s not found", __argv[1]); | |||
} | } | ||
}else{ | }else{ | ||
ShowBanner(); | ShowBanner(); | ||
Warn.Log("Usage:"); | |||
Warn.Log(" ucc CommandletName <parameters>"); | |||
} | } | ||
Line 275: | Line 229: | ||
GIsGuarded = 0; | GIsGuarded = 0; | ||
}catch(...){ | }catch(...){ | ||
GIsGuarded = 0; | GIsGuarded = 0; | ||
Error.HandleError(); | Error.HandleError(); | ||
Line 281: | Line 234: | ||
appExit(); | appExit(); | ||
appRequestExit(1); //Needed in order to prevent this function from returning to SWRepublicCommando.exe | |||
} | } | ||
</source> | </source> |
Revision as of 00:18, 2 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 code compiles to a dll. In order to use it, SWRepublicCommando.exe needs to be modified so
* 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
* 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 <Windows.h>
#include <cstdlib> //__argc, __argv
#include "../../Engine/Inc/Engine.h" //Core and Engine
#include "../../Core/Inc/FOutputDeviceFile.h"
#include "../../Core/Inc/FOutputDeviceWindowsError.h" //For modal error messages that are not displayed in the console
#include "../../Core/Inc/FFeedbackContextAnsi.h"
namespace{
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
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){
char 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 to not get to fgets again
if(CurrentCmd.Caps() == "EXIT" || CurrentCmd.Caps() == "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(){
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();
//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.Num() > 0){
GEngine->Exec(*CurrentCmd, *GWarn);
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(){
GWarn->Log("=======================================");
GWarn->Log("ucc.exe for Star Wars Republic Commando");
GWarn->Log("made by Leon0628");
GWarn->Log("=======================================\n");
}
}
/*
* 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*/){
try{
GIsStarted = 1;
GIsGuarded = 1;
appInit(InPackage, InCmdLine, &Log, &Error, &Warn, ConfigFactory, 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 == "Make")
LoadFlags |= LOAD_DisallowFiles; //Not entirely sure what this does but the original ucc has it as well...
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 if(Commandlet->FindFunction(NAME_Main))
Commandlet->Main(GetIntSizeString(CommandletCmdLine)); //If UnrealScript main function is found this overload has to be used
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;
Error.HandleError();
}
appExit();
appRequestExit(1); //Needed in order to prevent this function from returning to SWRepublicCommando.exe
}