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
 
(19 intermediate revisions by 3 users not shown)
Line 1: Line 1:
Author: Leon
UCC is a command line utility for early Unreal engine games. Star Wars: Republic Commando ships without one, but a custom one has been made available as a result of reverse engineering.


Used Tools: [[MS Visual Studio 2003]]
Description: A custom UCC.exe for Republic Commando. Used for executing Unreal Commandlets.


Description: A self written UCC for Republic Commando. Used for executing Unreal Commandlets.
Latest Build: [https://github.com/SWRC-Modding/CT/releases here]


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


Latest Build: [http://www.moddb.com/games/star-wars-republic-commando/downloads/star-wars-republic-commando-ucc-exe here]
[[File:Uccshow.PNG]]


===== Batchexport Commandlet =====


==UCC.cpp==
The batchexport commandlet is made available by UCC. This commandlet can parse the game's resource archives in order to export any exportable type in bulk. Here is the syntax for converting some texture package, called "exampletexturepackage.utx" from the game into a set of tga files in ExampleOutputFolder:


<source lang="cpp" line">
'''<sup>./ucc.exe batchexport exampletexturepackage.utx texture tga ".\\ExampleOutputFolder"</sup>'''
/*
* 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")
In particular, UCC can extract batches of sounds from the UAX archives where Republic Commando's voice lines and sound effects are stored. The batchexport commandlet does this. See [[Extract_Game_Audio_Using_UCC|how to extract game audio using UCC]] for details.
#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
}
</source>

Latest revision as of 20:05, 4 October 2023

UCC is a command line utility for early Unreal engine games. Star Wars: Republic Commando ships without one, but a custom one has been made available as a result of reverse engineering.

Description: A custom UCC.exe for Republic Commando. Used for executing Unreal Commandlets.

Latest Build: here


Uccshow.PNG

Batchexport Commandlet

The batchexport commandlet is made available by UCC. This commandlet can parse the game's resource archives in order to export any exportable type in bulk. Here is the syntax for converting some texture package, called "exampletexturepackage.utx" from the game into a set of tga files in ExampleOutputFolder:

./ucc.exe batchexport exampletexturepackage.utx texture tga ".\\ExampleOutputFolder"

In particular, UCC can extract batches of sounds from the UAX archives where Republic Commando's voice lines and sound effects are stored. The batchexport commandlet does this. See how to extract game audio using UCC for details.