project-bo4 / shield-development

GNU General Public License v3.0
193 stars 38 forks source link

Add a gsc component and basic hud functions #27

Closed ate47 closed 11 months ago

ate47 commented 11 months ago

Hello,

This pull request is adding a component and a config related to the GSC VMs.

It adds the config { "gsc" : { "dev_funcs" : false } } to enable dev functions. Most of them are nulled and useless (search for type = 1 in funcs.csv), but some are still interesting like

Example of AssertMsg("hello world"):

image

I've also add code to display the GSC errors in the logs. Except rare cases with Treyarch's code or bad script modders, it won't write anything in the logs for most of the users.

Then I've created few GSC/CSC functions to have a basic HUD, all with the prefix Shield:

// display message in the logs
ShieldLog(message);

// create a hud element with an id and some values
ShieldRegisterHudElem(id, text = "", color = 0xFFFFFFFF, x = 0, y = 0, anchor_x = 0, anchor_y = 0, align_x = 0, align_y = 0, scale = 1.0);
// remove a hud element by its id
ShieldRemoveHudElem(id);
// clear the hud elements
ShieldClearHudElems();
// set a hud element value
ShieldHudElemSetText(id, text);
ShieldHudElemSetX(id, text);
ShieldHudElemSetY(id, text);
ShieldHudElemSetColor(id, color_vector);
ShieldHudElemSetColor(id, color_rgba);
ShieldHudElemSetColor(id, r, g, b);
ShieldHudElemSetScale(id, scale);

A hud element is a text on the screen in game, example with a basic zombies counter

ShieldRegisterHudElem(#"zombies_counter", "^1waiting for the zombies...", 
        0xFFFF0000, // color red
        -6, 6, // x/y
        2, 0, // anchor x/y
        2, 0, // align x/y
        0.75 // scale
    );

// [...]

ShieldHudElemSetText(#"zombies_counter", "^1Zombies: ^5" + count + " ^1(^5" + level.zombie_total + " ^1remaining)");

image

the aligns and anchors are based with these values:

enum AlignX {
  LEFT = 0,
  CENTER = 1,
  RIGHT = 2
};
enum AlignY {
  TOP = 0,
  MIDDLE = 1,
  BOTTOM = 2
};

The anchor is from which side of the screen the (x,y) are relative to and the align is written at this location.

Knowing no easily available CSC injector is available, the custom functions are available in both GSC and CSC, but the GSC functions are only working for the host side.

bodnjenie14 commented 11 months ago

gsc functions only work host side

csc functions work via p2p - tested

image

project-bo4 commented 11 months ago

thanks for your contribution in advance. your codding well met our required standards but I didnt quite understand what new features your component adds to game. can you add some simple explanation if possible? *I have been outa reversing and developing for a while and have lost my touch(tbh i didnt have much experience about vm game script system when i worked on project either)

ate47 commented 11 months ago

To quickly discribe it, the GSC VM is actually 2 VMs, one for the client (CSC) and one for the server (GSC), the server one is handling how the gameplay is, the client one the FXs.

It's simple single-threaded script VMs in a C style able to call c++ functions from the game.

Scripts linking

The scripts are first loaded and linked by the game when a map is started. During this linking process the game will replace the function references (or imports) with pointers to the functions so the game don't have to search for them at runtime.

It is done inside

int GscObjResolve(scriptInstance_t, GSC_OBJ*); // 0x2746A30

with calls to: (Scr is server and CScr is client)

typedef void (*BuiltinFunction)(scriptInstance_t inst); // inst = 0 : GSC / inst = 1 : CSC

BuiltinFunction CScr_GetFunction(uint canonId, int *type, int *min_args, int *max_args); // 0x1F13140
BuiltinFunction CScr_GetMethod(uint canonId, int *type, int *min_args, int *max_args); // 0x1F13650
BuiltinFunction Scr_GetFunction(uint canonId, int *type, int *min_args, int *max_args); // 0x33AF840
BuiltinFunction Scr_GetMethod(uint canonId, int *type, int *min_args, int *max_args); // 0x33AFC20

These functions are the ones with a detour, in this PR what I did is to put a base to inject our own functions/methods, it's just checking if the returned function is null, if it's the case I added a code to check inside a custom list of functions (custom_functions_gsc for GSC and custom_functions_csc for CSC) and to return our own function if it is asked.

void* func = scr_get_function.invoke<void*>(name, type, min_args, max_args);

if (func)
{
    return func;
}

// ...

auto f = std::find_if(std::begin(custom_functions_gsc), std::end(custom_functions_gsc), [name](const game::BO4_BuiltinFunctionDef& func) { return func.canonId == name; });

if (f != std::end(custom_functions_gsc))
{
    *type = f->type && !enable_dev_func;
    *min_args = f->min_args;
    *max_args = f->max_args;

    return f->actionFunc;
}

return nullptr;

Like that we can implement custom functions and use them in a custom script.

Dev functions

Inside the game, you have normal functions, but also dev functions, these functions are usually nulled, (you have list here, type=1 = dev funcs) but some are still implemented, the only problem is, the game doesn't accept to link a function if *type is set to 1 by GetFunction or GetMethod and crash with a linking error. (1670707254)

What I've added in the detours and in the config is the gsc.dev_funcs boolean, if it is set to true, the client will set the type to normal every time and allow the call to dev functions. With a similar code if we want to implement custom dev functions.

Custom functions

Bod on Discord was asking for a small hud to do a zombies counter, so in my laziness I implemented some GSC custom functions to config basic text HUDs in the renderer scheduler. Even if the best would obviously be some LUI menu.

To conclude it's mostly useless things, except for when someone will add a code to load custom scripts or is using a script injector.