If you want to help us maintaining this wiki, check out our discord server: https://discord.gg/3u69jMa
Republic Commando UCC
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 '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 and because they're
* just pointers it's no problem to use them to call appInit from Core.dll.
* Also it's subsystem should be changed from window to console
* Everything compiles fine with Visual Studio .NET 2003 which is being used to achieve maximum compatibility
* since it was also used to compile Republic Commando
* 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")
#pragma comment(lib, "Engine.lib")
#include <Windows.h>
#include <iostream>
#include <cstdlib> //__argc, __argv
#include <cctype> //std::toupper
#include <string>
//Core and Engine
#include "Engine/Inc/Engine.h"
//For modal error messages that are not displayed in the console
#include "Core/Inc/FOutputDeviceWindowsError.h"
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...
//All commandlets that shipped with RC, used for autocompleting user input
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 which is an FFeedbackContextWindows
*/
void __stdcall FFeedbackContextAnsiSerialize(const TCHAR* V, EName Event){
TCHAR Buffer[1024]= "";
const TCHAR* Temp = V;
if(Event==NAME_Title){
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::cout << Temp << '\n';
FileLog->Serialize(V, Event);
}
/*
* Allows user input in the console when running a server
* This function runs in a separate thread, not sure if this is a good solution though...
*/
DWORD WINAPI UpdateServerConsoleInput(PVOID){
std::string cmd;
while(true){ //Ended at end of uccMain by ExitProcess
std::getline(std::cin,cmd);
GEngine->Exec(cmd.c_str(), *GWarn);
}
return 0;
}
/*
* Console handler that flushes the log file in case console is closed while running a server
* and thus prevents the log file from being empty
*/
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType){
if(dwCtrlType == CTRL_CLOSE_EVENT){
FileLog->Flush();
return TRUE;
}
return FALSE;
}
/*
* Replacement for UServerCommandlet::Main since the one from Engine.dll crashes because it doesn't assign a value to GEngine
*/
void UServerCommandletMain(){
SetConsoleCtrlHandler(HandlerRoutine, TRUE); //Setting handler routine that prevents the log from being empty
UClass* EngineClass = UObject::StaticLoadClass(UEngine::StaticClass(), NULL, "Engine.GameEngine", NULL, LOAD_NoFail, NULL);
GEngine = ConstructObject<UEngine>(EngineClass);
GEngine->Init();
GIsRunning = 1;
CreateThread(NULL, 0, UpdateServerConsoleInput, NULL, 0, NULL);
double OldTime = appSeconds();
//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;
}
/*
* Returns a specific property of a commandlet, valid properties are: IsClient, IsEditor, IsServer, LazyLoad, ShowErrorCount
* This function launches a Commandlet itself that gets the property in UnrealScript and returns it. This is necessary because
* the UT99 headers are (obviously) not fully compatible with RC and thus getting default proerties via UClass::GetDefaultObject
* doesn't work. This however, works fine. The only downside is that there needs to exist a package called UCC.u that contains this custom commandlet...
*/
int GetCommandletProperty(std::string Cmdlet, std::string Property){
//Static because there's no reason to reload the class and create the object each time this function is called
static UClass* Class = UObject::StaticLoadClass(UCommandlet::StaticClass(), NULL, "UCC.GetCommandletPropertiesCommandlet", NULL, LOAD_NoFail, NULL);
static UCommandlet* Commandlet = ConstructObject<UCommandlet>(Class);
Commandlet->InitExecution();
Commandlet->ParseParms(("CommandletClass=" + Cmdlet).c_str());
return Commandlet->Main(FString(Property.c_str()));
}
}
/*
* 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 for a better overview
std::string CommandletCmdLine; //Contains only the command-line options for the commandlet that is being executed to avoid problems with some commandlets
for(int i = 2; i < __argc; i++)
CommandletCmdLine += std::string(__argv[i]) + " ";
FileLog = InLog;
//vtable hack to have InWarn call a different Serialize function that prints to the console
//Have to do this because creating new FFeedbackContext and passing it to appInit results in a crash
//probaby due to different vtable layouts which so far has not been possible to fix.
//But since this works fine, there really is no reason to investigate the issue any further...
{//===============================================================
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;
std::string Token = ToUpper(__argv[1]);
std::string ClassName = Token;
DWORD LoadFlags = LOAD_NoWarn | LOAD_Quiet;
//Looking it up in list of default commandlets and if found constructing proper class name
for(int i = 0; i < ARRAY_COUNT(DefaultCommandlets); i++){
if(Token == ToUpper(DefaultCommandlets[i][1]) || //Name
Token == ToUpper(DefaultCommandlets[i][1]) + "COMMANDLET"){ //Name + "Commandlet"
ClassName = std::string(DefaultCommandlets[i][0]) + "." + DefaultCommandlets[i][1] + "Commandlet";
break;
}
}
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 = 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());
GIsClient = GetCommandletProperty(ClassName, "IsClient");
GIsEditor = GetCommandletProperty(ClassName, "IsEditor");
GIsServer = GetCommandletProperty(ClassName, "IsServer");
GLazyLoad = GetCommandletProperty(ClassName, "LazyLoad");
Commandlet->InitExecution();
Commandlet->ParseParms(CommandletCmdLine.c_str());
if(GetCommandletProperty(ClassName, "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)) //If no UnrealScript Main function is found, the commandlet is written in native code
Commandlet->Main(CommandletCmdLine.c_str());
else
Commandlet->Main(FString(CommandletCmdLine.c_str())); //For non-native commandlets this overload has to be used
if(GetCommandletProperty(ClassName, "ShowErrorCount"))
InWarn->Logf("\n%s - %i error(s), %i warnings", 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(); //For some reason this crashes (sometimes?) when there are compiler errors with ucc make
GIsGuarded = 0;
}catch(...){
GLog = InLog;
GIsGuarded = 0;
Error.HandleError();
}
appExit();
ExitProcess(ErrorCount == 0 ? EXIT_SUCCESS : EXIT_FAILURE); //Needed in order to prevent this function from returning to SWRepublicCommando.exe
}