|
|
(11 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 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
| |
| */
| |
|
| |
|
| #include <Windows.h>
| | 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. |
| | |
| #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;
| |
| }
| |
| | |
| 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*/){
| |
| 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 FFeedbackContextWindows, 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;
| |
| }//===============================================================
| |
| | |
| 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.Caps();
| |
| | |
| 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());
| |
| | |
| if(Default->ShowBanner)
| |
| ShowBanner();
| |
| | |
| 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{
| |
| ShowBanner();
| |
| InWarn->Logf("Commandlet %s not found", __argv[1]);
| |
| }
| |
| }else{
| |
| ShowBanner();
| |
| 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
| |
| }
| |
| </source>
| |