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 29: | Line 29: | ||
*/ | */ | ||
#pragma comment(lib, "Core.lib") | #pragma comment(lib, "Core/lib/Core.lib") | ||
#pragma comment(lib, "Engine.lib") | #pragma comment(lib, "Engine/lib/Engine.lib") | ||
#include <Windows.h> | #include <Windows.h> | ||
#include <cstdlib> //__argc, __argv | #include <cstdlib> //__argc, __argv | ||
#include <cctype> //std::toupper | #include <cctype> //std::toupper | ||
#include < | #include <cstdio> //puts, fgets | ||
//Core and Engine | #include "Engine/Inc/Engine.h" //Core and Engine | ||
#include " | #include "Core/Inc/FOutputDeviceWindowsError.h" //For modal error messages that are not displayed in the console | ||
//For modal error messages that are not displayed in the console | |||
namespace{ | namespace{ | ||
Line 50: | Line 47: | ||
int WarningCount = 0; //Incremented whenever a warning occurs in FFeedbackContextAnsiSerialize | int WarningCount = 0; //Incremented whenever a warning occurs in FFeedbackContextAnsiSerialize | ||
int ErrorCount = 0; //Same as WarningCount just with errors... | int ErrorCount = 0; //Same as WarningCount just with errors... | ||
/* | /* | ||
Line 138: | Line 78: | ||
} | } | ||
std:: | std::puts(Temp); | ||
FileLog->Serialize(V, Event); | FileLog->Serialize(V, Event); | ||
Line 148: | Line 88: | ||
*/ | */ | ||
DWORD WINAPI UpdateServerConsoleInput(PVOID){ | DWORD WINAPI UpdateServerConsoleInput(PVOID){ | ||
char Cmd[1024]; | |||
while(GIsRunning && !GIsRequestingExit){ | while(GIsRunning && !GIsRequestingExit){ | ||
std:: | if(std::fgets(Cmd, sizeof(Cmd), stdin)) | ||
GEngine->Exec(Cmd, *GWarn); | |||
} | } | ||
Line 196: | Line 135: | ||
GIsRunning = 0; | GIsRunning = 0; | ||
} | } | ||
} | } | ||
Line 219: | Line 141: | ||
* Entry point, called by modified SWRepublicCommando.exe | * Entry point, called by modified SWRepublicCommando.exe | ||
*/ | */ | ||
__declspec(dllexport) void uccMain(const TCHAR | __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*/){ | ||
FOutputDeviceWindowsError Error; //Error handling using message boxes which is just nicer | FOutputDeviceWindowsError Error; //Error handling using message boxes which is just nicer | ||
Line 225: | Line 147: | ||
FileLog = InLog; | 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); | PVOID* vtable = *reinterpret_cast<PVOID**>(InWarn); | ||
Line 249: | Line 172: | ||
GIsGuarded = 1; | GIsGuarded = 1; | ||
appInit( | appInit(InPackage, InCmdLine, InLog, &Error, InWarn, ConfigFactory, 1); | ||
UObject::SetLanguage("int"); | UObject::SetLanguage("int"); | ||
Line 256: | Line 179: | ||
GIsUCC = GIsClient = GIsServer = GIsEditor = GIsScriptable = GLazyLoad = 1; | 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(Token == "MAKE" || Token == "MAKECOMMANDLET" || Token == "EDITOR.MAKE" || Token == "EDITOR.MAKECOMMANDLET") | ||
LoadFlags |= LOAD_DisallowFiles; //Not sure what this does but the original ucc has it as well... | LoadFlags |= LOAD_DisallowFiles; //Not sure what this does but the original ucc has it as well... | ||
UClass* Class = LoadClass<UCommandlet>(NULL, | UClass* Class = LoadClass<UCommandlet>(NULL, *Token, 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, ( | Class = LoadClass<UCommandlet>(NULL, *(Token + "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){ | ||
UCommandlet* Commandlet = ConstructObject<UCommandlet>(Class); | UCommandlet* Commandlet = ConstructObject<UCommandlet>(Class); | ||
UCommandlet* Default = static_cast<UCommandlet*>(Class->GetDefaultObject()); | |||
InWarn->Logf("Executing %s\n", Class->GetFullName()); | InWarn->Logf("Executing %s\n", Class->GetFullName()); | ||
GIsClient = | GIsClient = Default->IsClient; | ||
GIsEditor = | GIsEditor = Default->IsEditor; | ||
GIsServer = | GIsServer = Default->IsServer; | ||
GLazyLoad = | GLazyLoad = Default->LazyLoad; | ||
FString CommandletCmdLine; //Contains only the command-line options that are passed to the commandlet to avoid problems with some commandlets | |||
for(int i = 2; i < __argc; i++) | for(int i = 2; i < __argc; i++) | ||
CommandletCmdLine += | CommandletCmdLine += FString(__argv[i]) + " "; | ||
Commandlet->InitExecution(); | Commandlet->InitExecution(); | ||
Commandlet->ParseParms(CommandletCmdLine | Commandlet->ParseParms(*CommandletCmdLine); | ||
if( | if(Default->LogToStdout) | ||
GLog = InWarn; //Redirecting commandlet output to console | GLog = InWarn; //Redirecting commandlet output to console | ||
if(Token == "SERVER" || Token == "SERVERCOMMANDLET" || Token == "ENGINE.SERVER" || Token == "ENGINE.SERVERCOMMANDLET") | if(Token == "SERVER" || Token == "SERVERCOMMANDLET" || Token == "ENGINE.SERVER" || Token == "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( | Commandlet->Main(*CommandletCmdLine); | ||
if( | if(Default->ShowErrorCount) | ||
InWarn->Logf("\n%s - %i error(s), %i | InWarn->Logf("\n%s - %i error(s), %i warning(s)", ErrorCount == 0 ? "Success" : "Failure", ErrorCount, WarningCount); | ||
GLog = InLog; | GLog = InLog; | ||
}else{ | }else{ | ||
InWarn->Logf("Commandlet %s not found", | InWarn->Logf("Commandlet %s not found", __argv[1]); | ||
} | } | ||
}else{ | }else{ |
Revision as of 02:21, 17 January 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 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
* since it was also used to compile RC
* The following settings are required in order to compile everything without errors:
* - Character Set = Not Set
* - Struct Member Alignment = 4 Bytes
* - Calling Convention = __fastcall
*/
#pragma comment(lib, "Core/lib/Core.lib")
#pragma comment(lib, "Engine/lib/Engine.lib")
#include <Windows.h>
#include <cstdlib> //__argc, __argv
#include <cctype> //std::toupper
#include <cstdio> //puts, fgets
#include "Engine/Inc/Engine.h" //Core and Engine
#include "Core/Inc/FOutputDeviceWindowsError.h" //For modal error messages that are not displayed in the console
namespace{
//Global variables
FOutputDevice* FileLog; //Used by FFeedbackContextAnsiSerialize to write to the log file because GLog might be reassigned
int WarningCount = 0; //Incremented whenever a warning occurs in FFeedbackContextAnsiSerialize
int ErrorCount = 0; //Same as WarningCount just with errors...
/*
* Replacement for Serialize function of 'InWarn', passed to appInit which is an FFeedbackContextWindows
*/
void __stdcall FFeedbackContextAnsiSerialize(const TCHAR* V, EName Event){
TCHAR Buffer[1024]= "";
const TCHAR* Temp = V;
if(Event == NAME_Title){
SetConsoleTitle(V);
return; //Prevents the server from spamming the player count to the log
}else if(Event == NAME_Heading){
appSprintf(Buffer, "\n--------------------%s--------------------", V);
Temp = Buffer;
V = Buffer; //So that the log file also contains the formatted string
}else if(Event == NAME_Warning || Event == NAME_ExecWarning || Event == NAME_ScriptWarning){
appSprintf(Buffer, "%s: %s", *FName(Event), V);
WarningCount++;
Temp = Buffer;
}else if(Event == NAME_Error || Event == NAME_Critical){
appSprintf(Buffer, "%s: %s", *FName(Event), V);
ErrorCount++;
Temp = Buffer;
}
std::puts(Temp);
FileLog->Serialize(V, Event);
}
/*
* 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){
if(std::fgets(Cmd, sizeof(Cmd), stdin))
GEngine->Exec(Cmd, *GWarn);
}
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();
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;
if(Token == "MAKE" || Token == "MAKECOMMANDLET" || Token == "EDITOR.MAKE" || Token == "EDITOR.MAKECOMMANDLET")
LoadFlags |= LOAD_DisallowFiles; //Not sure what this does but the original ucc has it as well...
UClass* Class = LoadClass<UCommandlet>(NULL, *Token, NULL, LoadFlags, NULL);
if(!Class) //If class failed to load, appending "Commandlet" and trying again
Class = LoadClass<UCommandlet>(NULL, *(Token + "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){
UCommandlet* Commandlet = ConstructObject<UCommandlet>(Class);
UCommandlet* Default = static_cast<UCommandlet*>(Class->GetDefaultObject());
InWarn->Logf("Executing %s\n", Class->GetFullName());
GIsClient = Default->IsClient;
GIsEditor = Default->IsEditor;
GIsServer = Default->IsServer;
GLazyLoad = Default->LazyLoad;
FString CommandletCmdLine; //Contains only the command-line options that are passed to the commandlet to avoid problems with some commandlets
for(int i = 2; i < __argc; i++)
CommandletCmdLine += FString(__argv[i]) + " ";
Commandlet->InitExecution();
Commandlet->ParseParms(*CommandletCmdLine);
if(Default->LogToStdout)
GLog = InWarn; //Redirecting commandlet output to console
if(Token == "SERVER" || Token == "SERVERCOMMANDLET" || Token == "ENGINE.SERVER" || Token == "ENGINE.SERVERCOMMANDLET")
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
Commandlet->Main(*CommandletCmdLine);
if(Default->ShowErrorCount)
InWarn->Logf("\n%s - %i error(s), %i warning(s)", ErrorCount == 0 ? "Success" : "Failure", ErrorCount, WarningCount);
GLog = InLog;
}else{
InWarn->Logf("Commandlet %s not found", __argv[1]);
}
}else{
InWarn->Log("Usage:");
InWarn->Log(" ucc CommandletName <parameters>");
}
appPreExit();
GIsGuarded = 0;
}catch(...){
GLog = InLog;
GIsGuarded = 0;
Error.HandleError();
}
appExit();
std::exit(EXIT_SUCCESS); //Needed in order to prevent this function from returning to SWRepublicCommando.exe
}