diff options
author | Dylan Muller <dylan.muller@corigine.com> | 2023-09-16 12:34:28 +0200 |
---|---|---|
committer | Dylan Muller <dylan.muller@corigine.com> | 2023-09-17 19:34:45 +0200 |
commit | d9454c1ef1b140c21e096de80a98ce0b3e811681 (patch) | |
tree | 6a7a9963d7bf5898b1858c6feb7410da8aeef24b | |
download | gsctool-d9454c1ef1b140c21e096de80a98ce0b3e811681.tar.gz gsctool-d9454c1ef1b140c21e096de80a98ce0b3e811681.zip |
gsctool: core: initial commit
Initial commit of gsctool. A utility to inject custom GSC scripts
for Call of Duty Black Ops 1 (PC).
-rw-r--r-- | .gitmodules | 6 | ||||
-rw-r--r-- | Makefile | 13 | ||||
-rw-r--r-- | README.md | 31 | ||||
-rw-r--r-- | gsctool.c | 516 | ||||
-rw-r--r-- | gsctool.h | 96 | ||||
-rw-r--r-- | gsctool/config.ini | 3 | ||||
-rw-r--r-- | gsctool/spawn_raygun/main.gsc | 31 | ||||
-rw-r--r-- | images/demo.png | bin | 0 -> 51826 bytes | |||
m--------- | lib/cdl86 | 0 | ||||
m--------- | lib/miniz | 0 | ||||
-rw-r--r-- | offsets.h | 17 |
11 files changed, 713 insertions, 0 deletions
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..182a0cb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "lib/cdl86"] + path = lib/cdl86 + url = https://github.com/lunarjournal/cdl86.git +[submodule "lib/miniz"] + path = lib/miniz + url = https://github.com/lunarjournal/miniz.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..598cda1 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +CC=tcc.exe +CFLAGS=-m32 -shared -lmsvcrt +CFILES_GSC=gsctool.c lib/cdl86/cdl.c lib/miniz/miniz.c + +all: gsctool + +gsctool: gsctool.c + $(CC) $(CFLAGS) $(CFILES_GSC) -o gsctool.dll + +.PHONY: clean +clean: + rm -f gsctool.dll + diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f21000 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# gsctool
+
+Simple GSC loader and dumper for `Call Of Duty: Black Ops 1 (T5) (Microsoft Windows)`.
+
+To load the demo GSC plugin copy `gsctool` to the games root directory.
+
+This demo will give you a raygun on spawn in zombie mode (solo).
+
+* GSC dumps are written to `gsctool/cache`
+
+This project is intended to be a starting point for more advanced mods.
+
+# Instructions
+
+Run: `git submodule update --init --recursive` to clone submodules.
+
+Then build project using the provided makefile in Windows Subsystem for Linux
+and inject resulting DLL.
+
+The compiler used for this project is `tcc`.
+
+Note: You can modify and reload GSC scripts while Black Ops is running by quiting
+the level and starting it again.
+
+
+
+# Libraries
+
+* [cdl86](https://github.com/lunarjournal/cdl86) detour library
+* [miniz](https://github.com/lunarjournal/miniz) zlib library
+
diff --git a/gsctool.c b/gsctool.c new file mode 100644 index 0000000..9c8c39c --- /dev/null +++ b/gsctool.c @@ -0,0 +1,516 @@ +/**
+ * Written by: Dylan Muller
+ * Copyright (c) 2023
+ */
+
+/* Global includes */
+#include <Windows.h>
+#include <direct.h>
+
+/* Local includes */
+#include "offsets.h"
+#include "gsctool.h"
+#include "lib/miniz/miniz.h"
+#include "lib/cdl86/cdl.h"
+
+/* Local definitions */
+#define SIZE_BUF 1024 * 1024
+#define SIZE_PATH 256
+
+/* Assign function pointers */
+/* Load GSC script */
+Scr_LoadScript_t Scr_LoadScript = (Scr_LoadScript_t)T5_Scr_LoadScript;
+/* Get VM function handle */
+Scr_GetFunctionHandle_t Scr_GetFunctionHandle = (Scr_GetFunctionHandle_t)T5_Scr_GetFunctionHandle;
+/* Create new execution thread */
+Scr_ExecThread_t Scr_ExecThread = (Scr_ExecThread_t)T5_Scr_ExecThread;
+/* Free execution thread */
+Scr_FreeThread_t Scr_FreeThread = (Scr_FreeThread_t)T5_Scr_FreeThread;
+/* Execute on level startup */
+Scr_LoadGameType_t Scr_LoadGameType = (Scr_LoadGameType_t)T5_Scr_LoadGameType;
+/* Dvar lookup */
+Dvar_FindVar_t Dvar_FindVar = (Dvar_FindVar_t)T5_Dvar_FindVar;
+/* Link asset/rawfile. Should be called before Scr_LoadScript */
+DB_LinkXAssetEntry_t DB_LinkXAssetEntry = (DB_LinkXAssetEntry_t)T5_DB_LinkXAssetEntry;
+/* Hotfix to prevent crash */
+Assign_Hotfix_t Assign_Hotfix = (Assign_Hotfix_t)T5_Assign_Hotfix;
+/* Remove anti-debug timer */
+Thread_Timer_t Thread_Timer = (Thread_Timer_t)T5_Thread_Timer;
+
+/* Global variables */
+int* init_trigger = (int*)T5_init_trigger;
+uint8_t mod_dir[SIZE_PATH];
+uint8_t mod_dir_relative[SIZE_PATH];
+uint8_t entry_file[SIZE_PATH];
+int func_handle = 0;
+
+/* Strip extension from name */
+void strip_ext(uint8_t* fname)
+{
+ uint8_t* end = fname + strlen(fname);
+
+ while (end > fname && *end != '.')
+ {
+ --end;
+ }
+
+ if (end > fname)
+ {
+ *end = '\0';
+ }
+}
+
+/* Print banner */
+void print_banner()
+{
+ printf("[info] gsctool init\n");
+ printf("[info] gsc dumper and loader\n");
+}
+
+/* Print mod info (config.ini) */
+void print_mod_info(uint8_t* dir, uint8_t* entry)
+{
+ printf("mod.dir=%s\n", dir);
+ printf("mod.entry=%s\n", entry);
+}
+
+/* Print example config.ini */
+void print_mod_example()
+{
+ printf("example config.ini:\n");
+ printf("mod.dir=example\n");
+ printf("mod.entry=main.gsc\n");
+}
+
+/* Check if file exists */
+BOOL FileExists(LPCSTR szPath)
+{
+ DWORD attrib = GetFileAttributesA(szPath);
+ return (attrib != INVALID_FILE_ATTRIBUTES
+ && !(attrib & FILE_ATTRIBUTE_DIRECTORY));
+}
+
+/* Check if directory */
+BOOL DirectoryExists(LPCSTR szPath)
+{
+ DWORD attrib = GetFileAttributesA(szPath);
+ return (attrib != INVALID_FILE_ATTRIBUTES
+ && (attrib & FILE_ATTRIBUTE_DIRECTORY));
+}
+
+/* Hotfix to prevent crash when exiting gamemode. */
+int* __cdecl Assign_Hotfix_hk(
+ int* a1, int* a2
+)
+{
+ if (a1 != NULL && a2 != NULL)
+ {
+ return Assign_Hotfix(a1, a2);
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+/* AC bypass to allow detouring. */
+void __cdecl Thread_Timer_hk(
+ uint8_t a1, int a2,
+ int a3, int a4
+)
+{
+ return;
+}
+
+/* Load our custom GSC script */
+int32_t __cdecl Scr_LoadScript_hk(
+ int32_t scriptInstance,
+ const uint8_t* scriptName
+)
+{
+ uint8_t script_file[SIZE_PATH];
+ uint8_t mapname_buffer[SIZE_PATH];
+ uint8_t* mapname = NULL;
+ FILE* gsc_script = NULL;;
+ uint8_t* source_buffer = 0x0;
+ uint8_t* data_buffer = 0x0;
+ uint8_t* compress_buffer = 0x0;
+ uint32_t gsc_size = 0x0;
+ int cmp_status = 0x0;
+ RawFileData* file_data = 0x0;
+ mz_ulong gsc_compress_size = 0x0;
+ mz_ulong data_size = 0;
+ XAsset entry = { 0 };
+ int ret = 0x0;
+ int result = 0x0;
+
+ result = Scr_LoadScript(scriptInstance, scriptName);
+
+ /* Get current map name */
+ mapname = Dvar_FindVar("mapname");
+ sprintf(mapname_buffer, "maps/%s", mapname);
+
+ /* Are we loading script for current map? */
+ if (strcmp(mapname_buffer, scriptName) == 0
+ && strcmp(mapname_buffer, "maps/frontend") != 0)
+ {
+ /* Load GSC script to inject */
+ FILE* gsc_script = fopen(entry_file, "rb");
+ if (gsc_script)
+ {
+ /* Get GSC script size in bytes */
+ fseek(gsc_script, 0, SEEK_END);
+ gsc_size = ftell(gsc_script);
+ fseek(gsc_script, 0, SEEK_SET);
+
+ /* Allocate GSC script text buffer */
+ source_buffer = (uint8_t*)malloc((sizeof(uint8_t) * gsc_size) + 1);
+
+ /* Return if we cannot allocate memory */
+ if (!source_buffer)
+ {
+ printf("[info] failed allocating memory\n");
+ fclose(gsc_script);
+ return result;
+ }
+ /* Read GSC script into buffer */
+ fread(source_buffer, sizeof(uint8_t), gsc_size, gsc_script);
+
+ /* Don't forget null terminator! */
+ source_buffer[gsc_size] = 0;
+
+ /* Estimate compression bounds for allocator */
+ gsc_compress_size = mz_compressBound(gsc_size + 1);
+ /* Allocate data buffer for compressed data */
+ data_buffer = (uint8_t*)malloc((sizeof(uint8_t) * gsc_compress_size)
+ + sizeof(RawFileData));
+
+ /* Return if we cannot allocate memory */
+ if (!data_buffer)
+ {
+ printf("[info] failed allocating memory\n");
+ fclose(gsc_script);
+ free(source_buffer);
+ return result;
+ }
+ compress_buffer = (uint8_t*)(data_buffer + sizeof(RawFileData));
+ /* Apply zlib compression */
+ cmp_status = mz_compress((uint8_t*)compress_buffer, &gsc_compress_size,
+ (uint8_t*)source_buffer, gsc_size + 1);
+ /* Now free source buffer */
+ free(source_buffer);
+
+ if (cmp_status == MZ_OK){
+ /* If compression succeeded set compression and deflate lengths */
+ if (data_buffer) {
+ file_data = (RawFileData*)data_buffer;
+ file_data->compressedSize = gsc_compress_size;
+ file_data->deflatedSize = gsc_size + 1;
+ }
+
+ /* Calculate total payload size */
+ data_size = gsc_compress_size + sizeof(RawFileData);
+ /* Set entry type */
+ entry.type = ASSET_TYPE_RAWFILE;
+ entry.header.rawFile = (RawFile*)malloc(sizeof(RawFile));
+
+ /* Return if we cannot allocate memory */
+ if (!entry.header.rawFile)
+ {
+ printf("[info] failed allocating memory\n");
+ fclose(gsc_script);
+ free(data_buffer);
+ return result;
+ }
+ entry.header.rawFile->buffer = data_buffer;
+ entry.header.rawFile->len = data_size;
+ entry.header.rawFile->name = (uint8_t*)entry_file;
+
+ printf("[info] linking asset %s\n", entry_file);
+ /* Link script asset before compilation */
+ DB_LinkXAssetEntry(&entry, 0);
+
+ sprintf(script_file, "%s", entry_file);
+ strip_ext(script_file);
+ /* Compile GSC script */
+ ret = Scr_LoadScript(0, script_file);
+
+ /* Free asset */
+ free(data_buffer);
+ free(entry.header.rawFile);
+
+ printf("[info] retreiving function handle\n");
+ /* Get main VM entry point */
+ func_handle = Scr_GetFunctionHandle(0, script_file, "main");
+ if (func_handle)
+ {
+ printf("[info] main @ %p\n", (uint8_t*)func_handle);
+ }
+ else
+ {
+ printf("[error] failed to retreive main handle\n");
+ }
+ fclose(gsc_script);
+ }
+ }
+ }
+
+ return result;
+}
+
+/* Execute function handle at correct timing */
+void __cdecl Scr_LoadGameType_hk()
+{
+ Scr_LoadGameType();
+ uint8_t* mapname = Dvar_FindVar("mapname");
+
+ if (strcmp(mapname, "frontend") == 0)
+ {
+ printf("[info] loaded main menu\n");
+ }
+ else if (func_handle > 0)
+ {
+ printf("[info] executing main @ %p\n", (uint8_t*)func_handle);
+ int16_t handle = Scr_ExecThread(0, func_handle, 0);
+ Scr_FreeThread(handle, 0);
+ }
+ return;
+}
+
+/* Recursively make directory */
+void __cdecl mkdir_recursive(uint8_t* path)
+{
+ int i = 0x0;
+ uint8_t* buffer = NULL;
+
+ if (path != NULL) {
+ buffer = (uint8_t*)malloc(SIZE_PATH);
+ if (!buffer)
+ {
+ printf("[info] failed allocating memory\n");
+ return;
+ }
+ int len_path = strlen(path);
+ for (i = 0; i < len_path; i++)
+ {
+ if (path[i] == '/')
+ {
+ strncpy(buffer, path, i);
+ buffer[i] = 0;
+ mkdir(buffer);
+ }
+ }
+ free(buffer);
+ }
+
+
+}
+
+/* Dump GSC and CSC scripts as they are loaded */
+XAssetEntryPoolEntry* __cdecl DB_LinkXAssetEntry_hk(
+ XAsset* newEntry,
+ int32_t allowOverride
+)
+{
+ struct RawFile* rawfile = newEntry->header.rawFile;
+ const uint8_t* data = 0x0;
+ uint8_t* buffer = (uint8_t*)malloc(SIZE_BUF);
+ uint8_t* path = (uint8_t*)malloc(SIZE_PATH);
+ uint8_t* script_name = 0x0;
+ z_stream stream;
+ int status = 0x0;
+ int inflate_len = 0x0;
+ FILE* inflated_script = 0x0;
+
+ if (!buffer || !path)
+ {
+ printf("[info] failed allocating memory\n");
+ return DB_LinkXAssetEntry(newEntry, allowOverride);
+ }
+
+ if (newEntry->type == ASSET_TYPE_RAWFILE && rawfile!= NULL)
+ {
+ script_name = rawfile->name;
+
+
+ /* Check for GSC or CSC script */
+ if (strstr(script_name, ".gsc") != NULL
+ || strstr(script_name, ".csc") != NULL)
+ {
+ memset(&stream, 0, sizeof(stream));
+ /* Apply file data offset */
+ data = (const uint8_t*)(rawfile->buffer + FILE_OFFSET);
+
+ /* Setup decompression stream */
+ stream.next_in = data;
+ stream.avail_in = rawfile->len - FILE_OFFSET;
+ stream.next_out = buffer;
+ stream.avail_out = SIZE_BUF;
+
+ /* Initialize inflate with stream */
+ inflateInit(&stream);
+ /* Decompress script... */
+ status = inflate(&stream, Z_SYNC_FLUSH);
+ inflateEnd(&stream);
+ inflate_len = stream.total_out;
+
+ /* On decompression success create dir and write file */
+ if (status == Z_STREAM_END || status == Z_OK)
+ {
+ sprintf(path, "%s/%s", PATH_DUMP, script_name);
+ printf("[cache] %s\n", path);
+ mkdir_recursive(path);
+ inflated_script = fopen(path, "wb");
+ if (inflated_script)
+ {
+ fwrite(buffer, sizeof(uint8_t), inflate_len, inflated_script);
+ fclose(inflated_script);
+ }
+ }
+
+ }
+ }
+ free(buffer);
+ free(path);
+
+ return DB_LinkXAssetEntry(newEntry, allowOverride);
+}
+
+/* Main thread to execute once injected. */
+DWORD WINAPI init_thread(LPVOID lpParam)
+{
+ FILE* dependency_list = NULL;
+ FILE* config_file = NULL;
+ uint8_t* mod_dir_rel = (uint8_t*)malloc(SIZE_PATH);
+ uint8_t* entry_file_rel = (uint8_t*)malloc(SIZE_PATH);
+
+ /* Wait until all threads have started. */
+ while (! *init_trigger) { Sleep(10); }
+
+ /* Allocate console and redirect std io. */
+ AllocConsole();
+ freopen("CONOUT$", "w", stdout);
+ freopen("CONIN$", "r", stdin);
+
+ if (!mod_dir_rel || !entry_file_rel)
+ {
+ printf("[info] failed allocating memory\n");
+ return EXIT_FAILURE;
+ }
+ /* Print startup banner */
+ print_banner();
+
+ mkdir(PATH_ROOT);
+
+ config_file = fopen(PATH_ROOT "/config.ini", "r");
+ if (!config_file)
+ {
+ printf("[error] %s", "Error opening config.ini\n");
+ return EXIT_SUCCESS;
+ }
+
+ printf("[info] loaded config.ini\n");
+
+ if (fscanf(config_file, "mod.dir=%s\n", mod_dir_rel) != 1)
+ {
+ printf("[error] %s\n", "failed reading 'mod.dir' key in config file\n");
+ print_mod_example();
+ return EXIT_SUCCESS;
+ }
+
+ if (fscanf(config_file, "mod.entry=%s\n", entry_file_rel) != 1)
+ {
+ printf("[error] %s\n", "failed reading 'mod.entry' from config file\n");
+ print_mod_example();
+ return EXIT_SUCCESS;
+ }
+
+ fclose(config_file);
+
+ printf("[info] mod info\n");
+ print_mod_info(mod_dir_rel, entry_file_rel);
+
+ sprintf(mod_dir, "%s/%s", PATH_ROOT, mod_dir_rel);
+ sprintf(mod_dir_relative, "%s", mod_dir_rel);
+
+ printf("[info] searching for mod directory...\n");
+ printf("%s\n", mod_dir);
+ if (!DirectoryExists(mod_dir))
+ {
+ printf("[error] %s\n", "mod directory does not exist");
+ return EXIT_SUCCESS;
+ }
+
+ printf("[info] mod directory found\n");
+ sprintf(entry_file, "%s/%s", mod_dir, entry_file_rel);
+
+ free(entry_file_rel);
+ free(mod_dir_rel);
+
+ printf("[info] searching for mod entry...\n");
+
+ if (!FileExists(entry_file))
+ {
+ printf("[error] %s\n", "mod entry file does not exist, check config!");
+ return EXIT_SUCCESS;
+ }
+
+ printf("[info] mod entry found\n");
+ printf("[info] applying detours...\n");
+
+ /* Subroutine patches. */
+ struct cdl_jmp_patch thread_timer =
+ cdl_jmp_attach((void**)&Thread_Timer, Thread_Timer_hk);
+ struct cdl_jmp_patch assign_hotfix =
+ cdl_jmp_attach((void**)&Assign_Hotfix, Assign_Hotfix_hk);
+ struct cdl_jmp_patch link_asset =
+ cdl_jmp_attach((void**)&DB_LinkXAssetEntry, DB_LinkXAssetEntry_hk);
+ struct cdl_jmp_patch load_script =
+ cdl_jmp_attach((void**)&Scr_LoadScript, Scr_LoadScript_hk);
+ struct cdl_jmp_patch load_game_type =
+ cdl_jmp_attach((void**)&Scr_LoadGameType, Scr_LoadGameType_hk);
+
+ /* Print debug info */
+ printf("[info] Thread_Timer() detour info\n");
+ cdl_jmp_dbg(&thread_timer);
+ printf("[info] Assign_Hotfix() detour info\n");
+ cdl_jmp_dbg(&assign_hotfix);
+ printf("[info] DB_LinkXAssetEntry() detour info\n");
+ cdl_jmp_dbg(&link_asset);
+ printf("[info] Scr_LoadScript() detour info\n");
+ cdl_jmp_dbg(&load_script);
+ printf("[info] Scr_LoadGameType() detour info\n");
+ cdl_jmp_dbg(&load_game_type);
+
+ /* Flush std io */
+ while (1)
+ {
+ flushall();
+ Sleep(200);
+ }
+
+ return EXIT_SUCCESS;
+
+}
+
+/* Main entry point */
+BOOL APIENTRY DllMain(
+ HMODULE hModule,
+ DWORD ul_reason_for_call,
+ LPVOID lpReserved
+)
+{
+ switch (ul_reason_for_call)
+ {
+ case DLL_PROCESS_ATTACH:
+ CreateThread((LPSECURITY_ATTRIBUTES)0, 0, (LPTHREAD_START_ROUTINE)init_thread,
+ (LPVOID)hModule, 0, (LPDWORD)NULL);
+ break;
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ case DLL_PROCESS_DETACH:
+ break;
+ }
+ return TRUE;
+}
+
diff --git a/gsctool.h b/gsctool.h new file mode 100644 index 0000000..8737bc6 --- /dev/null +++ b/gsctool.h @@ -0,0 +1,96 @@ +#ifndef _MAIN_H
+#define _MAIN_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define PATH_ROOT "gsctool"
+#define PATH_DUMP PATH_ROOT "/cache"
+#define FILE_OFFSET 0x8
+
+typedef struct RawFileData
+{
+ int32_t deflatedSize;
+ int32_t compressedSize;
+} RawFileData;
+
+typedef struct RawFile
+{
+ uint8_t* name;
+ int32_t len;
+ uint8_t* buffer;
+} RawFile;
+
+typedef enum XAssetType
+{
+ ASSET_TYPE_RAWFILE = 0x24
+} XAssetType;
+
+typedef union XAssetHeader
+{
+ struct RawFile* rawFile;
+ void* data;
+} XAssetHeader;
+
+typedef struct XAsset
+{
+ enum XAssetType type;
+ union XAssetHeader header;
+} XAsset;
+
+typedef struct XAssetEntry
+{
+ XAsset asset;
+} XAssetEntry;
+
+typedef union XAssetEntryPoolEntry
+{
+ struct XAssetEntry entry;
+ union XAssetEntryPoolEntry* next;
+} XAssetEntryPoolEntry;
+
+typedef int32_t (__cdecl* Scr_LoadScript_t)(
+ int32_t scriptInstance,
+ const uint8_t* scriptName
+);
+
+typedef int32_t (__cdecl* Scr_GetFunctionHandle_t)(
+ int32_t scriptInstance,
+ const uint8_t* scriptName,
+ const uint8_t* functioName
+);
+
+typedef uint16_t (__cdecl* Scr_ExecThread_t)(
+ int32_t scriptInstance,
+ int32_t handle,
+ int32_t paramCount
+);
+
+typedef uint16_t (__cdecl* Scr_FreeThread_t)(
+ uint16_t handle,
+ int scriptInstance
+);
+
+typedef XAssetEntryPoolEntry* (__cdecl* DB_LinkXAssetEntry_t)(
+ XAsset* newEntry,
+ int32_t allowOverride
+);
+
+typedef uint8_t* (__cdecl* Dvar_FindVar_t)(
+ uint8_t* variable
+);
+
+typedef int* (__cdecl* Assign_Hotfix_t)(
+ int* a1, int* a2
+);
+
+typedef void (__cdecl* Thread_Timer_t)(
+ uint8_t a1, int a2,
+ int a3, int a4
+);
+
+typedef void (__cdecl* Scr_LoadGameType_t)(
+ void
+);
+
+#endif
diff --git a/gsctool/config.ini b/gsctool/config.ini new file mode 100644 index 0000000..b29db9c --- /dev/null +++ b/gsctool/config.ini @@ -0,0 +1,3 @@ +mod.dir=spawn_raygun +mod.entry=main.gsc + diff --git a/gsctool/spawn_raygun/main.gsc b/gsctool/spawn_raygun/main.gsc new file mode 100644 index 0000000..b1fef79 --- /dev/null +++ b/gsctool/spawn_raygun/main.gsc @@ -0,0 +1,31 @@ +#include maps\_utility;
+#include common_scripts\utility;
+#include maps\_hud_util;
+
+main()
+{
+ level thread onPlayerConnect();
+}
+
+onPlayerConnect()
+{
+ for(;;)
+ {
+ level waittill("connected", player);
+ player thread onPlayerSpawned();
+ }
+}
+
+onPlayerSpawned()
+{
+ self endon("disconnect");
+ for(;;)
+ {
+ self waittill("spawned_player");
+ self iprintln("Spawned raygun!");
+ self GiveWeapon("ray_gun_zm");
+ self SetWeaponAmmoStock("ray_gun_zm", 1000);
+ self SwitchToWeapon("ray_gun_zm");
+
+ }
+}
diff --git a/images/demo.png b/images/demo.png Binary files differnew file mode 100644 index 0000000..a70522b --- /dev/null +++ b/images/demo.png diff --git a/lib/cdl86 b/lib/cdl86 new file mode 160000 +Subproject a3655fbccb17dbada53201bbcb22d641f7e5610 diff --git a/lib/miniz b/lib/miniz new file mode 160000 +Subproject e55522aa315852f79f24483e7f296c77e25f825 diff --git a/offsets.h b/offsets.h new file mode 100644 index 0000000..0683701 --- /dev/null +++ b/offsets.h @@ -0,0 +1,17 @@ +#ifndef _OFFSETS_H
+#define _OFFSETS_H
+
+/* Offsets for Call of Duty: Black Ops 1 (Win32) */
+/* Found using IDA */
+#define T5_Scr_LoadScript 0x00661AF0
+#define T5_Scr_GetFunctionHandle 0x004E3470
+#define T5_DB_LinkXAssetEntry 0x007A2F10
+#define T5_Scr_ExecThread 0x005598E0
+#define T5_Scr_FreeThread 0x005DE2C0
+#define T5_Scr_LoadGameType 0x004B7F80
+#define T5_Dvar_FindVar 0x0057FF80
+#define T5_Assign_Hotfix 0x007A4800
+#define T5_init_trigger 0x00C793B0
+#define T5_Thread_Timer 0x004C06E0
+
+#endif
|