diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..09e6422 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,75 @@ +cmake_minimum_required(VERSION 3.14) +project(cb2util C) + +set(CMAKE_C_STANDARD 99) + +# Include directories +include_directories( + . + include + libcheats/include + libbig_int/include +) + +# Source files +set(SOURCES + arcfour.c + cb2_crypto.c + cb2util.c + cbc.c + cheats.c + cheats_common.c + compress.c + fileio.c + getopt.c + pcb.c + shs.c + libcheats/src/cheatlist.c + libcheats/src/libcheats.c + libcheats/src/mystring.c + libcheats/src/parser.c + libbig_int/src/basic_funcs.c + libbig_int/src/bitset_funcs.c + libbig_int/src/low_level_funcs/add.c + libbig_int/src/low_level_funcs/and.c + libbig_int/src/low_level_funcs/andnot.c + libbig_int/src/low_level_funcs/cmp.c + libbig_int/src/low_level_funcs/div.c + libbig_int/src/low_level_funcs/mul.c + libbig_int/src/low_level_funcs/or.c + libbig_int/src/low_level_funcs/sqr.c + libbig_int/src/low_level_funcs/sub.c + libbig_int/src/low_level_funcs/xor.c + libbig_int/src/memory_manager.c + libbig_int/src/modular_arithmetic.c + libbig_int/src/number_theory.c + libbig_int/src/service_funcs.c + libbig_int/src/str_funcs.c +) + +add_definitions(-DCB2UTIL_VERSION="1.9" -DHAVE_STDINT_H) + +find_package(ZLIB QUIET) + +if(NOT ZLIB_FOUND) + include(FetchContent) + FetchContent_Declare( + zlib + URL https://github.com/madler/zlib/archive/refs/tags/v1.3.1.tar.gz + ) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + set(ZLIB_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(zlib) + set(ZLIB_INCLUDE_DIRS ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR}) + include_directories(${ZLIB_INCLUDE_DIRS}) +endif() + +add_executable(cb2util ${SOURCES}) + +if(TARGET zlibstatic) + target_link_libraries(cb2util PRIVATE zlibstatic) +elseif(TARGET zlib) + target_link_libraries(cb2util PRIVATE zlib) +else() + target_link_libraries(cb2util PRIVATE ZLIB::ZLIB) +endif() diff --git a/Makefile b/Makefile index 7619fe6..1aa21ae 100644 --- a/Makefile +++ b/Makefile @@ -2,27 +2,24 @@ all: # Define V=1 to have a more verbose compile. -# -# Define BUILD_MINGW=1 to cross-compile cb2util for Win32 using MinGW. -# Generate version info -CB2UTIL-VERSION-FILE: FORCE - @./CB2UTIL-VERSION-GEN --include CB2UTIL-VERSION-FILE -export CB2UTIL_VERSION +# Detect OS +ifeq ($(OS),Windows_NT) + IS_WIN = 1 +endif prefix = $(HOME) BIGINT = libbig_int LIBCHEATS = libcheats -CC = gcc -CURL = curl -INSTALL = install -RM = rm -f -SHA1SUM = sha1sum -STRIP = strip -TAR = tar -ZIP = zip +CC ?= gcc +CURL ?= curl +INSTALL ?= install +RM ?= rm -f +SHA1SUM ?= sha1sum +STRIP ?= strip +TAR ?= tar +ZIP ?= zip CFLAGS = -Wall -Werror -Wno-date-time -O2 CFLAGS += -I$(BIGINT)/include -I$(LIBCHEATS)/include -Iinclude @@ -30,7 +27,10 @@ CFLAGS += -DHAVE_STDINT_H LDFLAGS = LIBS = -ifeq ($(BUILD_MINGW),1) +ifeq ($(IS_WIN),1) + PROG = cb2util.exe + LIBS += -lz +else ifeq ($(BUILD_MINGW),1) CC = i586-mingw32msvc-gcc STRIP = i586-mingw32msvc-strip CFLAGS += -I$(ZLIB_PATH)/include -L$(ZLIB_PATH)/lib @@ -72,6 +72,7 @@ OBJS += cheats.o OBJS += cheats_common.o OBJS += compress.o OBJS += fileio.o +OBJS += getopt.o OBJS += pcb.o OBJS += shs.o @@ -119,18 +120,12 @@ $(PROG): $(OBJS) $(QUIET_LINK)$(CC) $(CFLAGS) -o $@ $(LDFLAGS) $(filter %.o,$^) $(LIBS) $(QUIET_STRIP)$(STRIP) $@ -cb2util.o: CB2UTIL-VERSION-FILE include/elf.h -cb2util.o: CFLAGS += -DCB2UTIL_VERSION='"$(CB2UTIL_VERSION)"' - -include/elf.h: - mkdir -p include - $(CURL) -fSs -o $@ https://gist.githubusercontent.com/mlafeldt/3885346/raw/elf.h +cb2util.o: CFLAGS += -DCB2UTIL_VERSION='"1.9"' clean: $(RM) $(PROG) $(OBJS) $(RM) CB2UTIL-VERSION-FILE - $(RM) -r release include - $(QUIET_SUBDIR0)test $(QUIET_SUBDIR1) clean + $(RM) -r release test: all $(QUIET_SUBDIR0)test $(QUIET_SUBDIR1) all @@ -138,7 +133,7 @@ test: all prove: all $(QUIET_SUBDIR0)test $(QUIET_SUBDIR1) prove -PACKAGE = cb2util-$(CB2UTIL_VERSION) +PACKAGE = cb2util-1.9 release: clean test $(RM) -r release/ $(INSTALL) -d -m 755 release/$(PACKAGE) diff --git a/README.md b/README.md index 3542dcd..0de0f22 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ The features are: ## Installation +### Linux & macOS (GCC/Clang) + To build cb2util from source simply run: $ git clone --recursive https://github.com/mlafeldt/cb2util @@ -25,9 +27,23 @@ To build cb2util from source simply run: $ make $ make install -Zig is also supported, which is particularly useful for cross-compiling: +### Windows (CMake / MinGW / MSVC) + +Using CMake (recommended on Windows): + + > cmake -B build + > cmake --build build + +The compiled `cb2util.exe` will be located in the `build/` directory (or `build/Debug/` / `build/Release/`). + +Using MinGW / GCC on Windows (PowerShell, Command Prompt, or MSYS2): + + > make + +### Cross-compiling / Zig + +Zig is also supported for native builds and cross-compiling without external dependencies: - $ make include/elf.h $ zig build $ zig build -Dtarget=aarch64-macos $ zig build -Dtarget=x86_64-linux-gnu diff --git a/build.zig b/build.zig index fb2e98b..0b7bbf6 100644 --- a/build.zig +++ b/build.zig @@ -91,6 +91,7 @@ pub fn build(b: *std.Build) void { "cheats_common.c", "compress.c", "fileio.c", + "getopt.c", "pcb.c", "shs.c", }, .flags = &cflags }); diff --git a/cb2util.c b/cb2util.c index 1544fda..8088ba3 100644 --- a/cb2util.c +++ b/cb2util.c @@ -14,6 +14,10 @@ #include #include +#ifndef CB2UTIL_VERSION +#define CB2UTIL_VERSION "1.9" +#endif + extern int cmd_cbc(int argc, char **argv); extern int cmd_cheats(int argc, char **argv); extern int cmd_pcb(int argc, char **argv); diff --git a/cbc.c b/cbc.c index 3f06faa..b4463fc 100644 --- a/cbc.c +++ b/cbc.c @@ -25,7 +25,7 @@ typedef struct { char gametitle[72]; uint32_t dataoff; /* file offset of data section */ uint32_t zero; /* always 0 */ - uint8_t data[0]; + uint8_t data[]; } cbc_hdr_t; #define CBC_FILE_ID 0x01554643 /* "CFU", 1 */ @@ -39,7 +39,7 @@ typedef struct { /* CBC v7 file header */ typedef struct { char gametitle[64]; - uint8_t data[0]; + uint8_t data[]; } cbc7_hdr_t; static const char *cbc_usage = @@ -168,21 +168,29 @@ int cmd_cbc(int argc, char **argv) goto compile_end; } +#if defined(__GNUC__) && __GNUC__ >= 7 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" +#pragma GCC diagnostic ignored "-Wstringop-truncation" +#endif if (v7) { cbc7_hdr_t hdr; memset(&hdr, 0, sizeof(hdr)); - strncpy(hdr.gametitle, GAMES_FIRST(&cheats.games)->title, 64); + snprintf(hdr.gametitle, sizeof(hdr.gametitle), "%s", GAMES_FIRST(&cheats.games)->title); fwrite(&hdr, sizeof(hdr), 1, fp); } else { cbc_hdr_t hdr; memset(&hdr, 0, sizeof(hdr)); hdr.fileid = CBC_FILE_ID; - strncpy((char*)hdr.rsasig, banner != NULL ? banner : CBC_BANNER, 256); + snprintf((char*)hdr.rsasig, sizeof(hdr.rsasig), "%s", banner != NULL ? banner : CBC_BANNER); hdr.cbvers = 0x0800; - strncpy(hdr.gametitle, GAMES_FIRST(&cheats.games)->title, 72); + snprintf(hdr.gametitle, sizeof(hdr.gametitle), "%s", GAMES_FIRST(&cheats.games)->title); hdr.dataoff = sizeof(hdr); fwrite(&hdr, sizeof(hdr), 1, fp); } +#if defined(__GNUC__) && __GNUC__ >= 7 +#pragma GCC diagnostic pop +#endif cb_crypt_data(buf, buflen); fwrite(buf, buflen, 1, fp); diff --git a/cheats.c b/cheats.c index 4a6f628..67a5f83 100644 --- a/cheats.c +++ b/cheats.c @@ -21,7 +21,7 @@ typedef struct { uint32_t fileid; /* must be CHEATS_FILE_ID */ uint32_t unknown; /* always 0x00010000 */ - uint8_t data[0]; + uint8_t data[]; } cheats_hdr_t; #define CHEATS_FILE_ID 0x00554643 /* "CFU\0" */ diff --git a/getopt.c b/getopt.c new file mode 100644 index 0000000..63cf136 --- /dev/null +++ b/getopt.c @@ -0,0 +1,137 @@ +/* + * Portable getopt / getopt_long implementation for cb2util + */ + +#include +#include +#include "getopt.h" + +#ifndef HAVE_SYSTEM_GETOPT + +char *optarg = NULL; +int optind = 1; +int opterr = 1; +int optopt = '?'; + +static int nextchar_idx = 1; + +int getopt(int argc, char * const argv[], const char *optstring) +{ + return getopt_long(argc, argv, optstring, NULL, NULL); +} + +int getopt_long(int argc, char * const argv[], const char *optstring, + const struct option *longopts, int *longindex) +{ + optarg = NULL; + + if (optind >= argc || argv[optind] == NULL) + return -1; + + char *arg = argv[optind]; + + if (arg[0] != '-' || arg[1] == '\0') + return -1; + + if (arg[1] == '-' && arg[2] == '\0') { + optind++; + return -1; + } + + /* Process long option */ + if (arg[1] == '-' && longopts != NULL) { + const char *name = arg + 2; + const char *val_ptr = strchr(name, '='); + size_t name_len = val_ptr ? (size_t)(val_ptr - name) : strlen(name); + + for (int i = 0; longopts[i].name != NULL; i++) { + if (strncmp(longopts[i].name, name, name_len) == 0 && + longopts[i].name[name_len] == '\0') { + optind++; + if (longindex) + *longindex = i; + + if (longopts[i].has_arg == required_argument) { + if (val_ptr) { + optarg = (char *)(val_ptr + 1); + } else if (optind < argc) { + optarg = argv[optind++]; + } else { + if (opterr) + fprintf(stderr, "%s: option '--%s' requires an argument\n", argv[0], name); + return '?'; + } + } else if (longopts[i].has_arg == optional_argument) { + if (val_ptr) + optarg = (char *)(val_ptr + 1); + } + + if (longopts[i].flag) { + *longopts[i].flag = longopts[i].val; + return 0; + } + return longopts[i].val; + } + } + + if (opterr) + fprintf(stderr, "%s: unrecognized option '--%s'\n", argv[0], name); + optind++; + return '?'; + } + + /* Process short option */ + char opt = arg[nextchar_idx]; + const char *spec = strchr(optstring, opt); + + if (spec == NULL || opt == ':') { + if (opterr) + fprintf(stderr, "%s: invalid option -- '%c'\n", argv[0], opt); + optopt = opt; + nextchar_idx++; + if (arg[nextchar_idx] == '\0') { + nextchar_idx = 1; + optind++; + } + return '?'; + } + + if (spec[1] == ':') { + if (spec[2] == ':') { + /* Optional argument */ + if (arg[nextchar_idx + 1] != '\0') { + optarg = &arg[nextchar_idx + 1]; + } + nextchar_idx = 1; + optind++; + } else { + /* Required argument */ + if (arg[nextchar_idx + 1] != '\0') { + optarg = &arg[nextchar_idx + 1]; + nextchar_idx = 1; + optind++; + } else if (optind + 1 < argc) { + optind++; + optarg = argv[optind++]; + nextchar_idx = 1; + } else { + if (opterr) + fprintf(stderr, "%s: option requires an argument -- '%c'\n", argv[0], opt); + optopt = opt; + nextchar_idx = 1; + optind++; + return '?'; + } + } + } else { + nextchar_idx++; + if (arg[nextchar_idx] == '\0') { + nextchar_idx = 1; + optind++; + } + } + + return opt; +} + +#endif /* HAVE_SYSTEM_GETOPT */ diff --git a/include/getopt.h b/include/getopt.h new file mode 100644 index 0000000..7dae19a --- /dev/null +++ b/include/getopt.h @@ -0,0 +1,44 @@ +/* + * Portable getopt / getopt_long interface for cb2util + */ + +#ifndef _GETOPT_H_ +#define _GETOPT_H_ + +#if defined(__GNUC__) && !defined(_WIN32) + #include_next + #define HAVE_SYSTEM_GETOPT 1 +#endif + +#ifndef HAVE_SYSTEM_GETOPT + +#ifdef __cplusplus +extern "C" { +#endif + +extern char *optarg; +extern int optind; +extern int opterr; +extern int optopt; + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +struct option { + const char *name; + int has_arg; + int *flag; + int val; +}; + +int getopt(int argc, char * const argv[], const char *optstring); +int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex); + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_SYSTEM_GETOPT */ + +#endif /* _GETOPT_H_ */ diff --git a/pcb.c b/pcb.c index a547cd6..a8f351e 100644 --- a/pcb.c +++ b/pcb.c @@ -13,14 +13,14 @@ #include #include #include -#include +#include "elf.h" #include "cb2_crypto.h" #include "fileio.h" /* PCB file header */ typedef struct { uint8_t rsasig[256]; /* 2048-bit RSA signature */ - uint8_t data[0]; + uint8_t data[]; } pcb_hdr_t; /* offset of data hashed for RSA signature */ diff --git a/test/cbc/heatseeker.cbc b/test/cbc/heatseeker.cbc new file mode 100644 index 0000000..19954ef Binary files /dev/null and b/test/cbc/heatseeker.cbc differ diff --git a/test/cbc/heatseeker.enc b/test/cbc/heatseeker.enc new file mode 100644 index 0000000..7270727 --- /dev/null +++ b/test/cbc/heatseeker.enc @@ -0,0 +1,97 @@ +"Heatseeker" +Enable Code (Must Be On) +B4336FA9 4DFEFB79 +ED99F9BD 23DFB976 +BD493B7F 9676A62A +FC10DFD0 3F0B6B61 +Disable FMV +7BAD4BEF 7DFBE69B +Debug Menu +2A6F25FB ADB97FE5 +Unlock All Planes/Bonus Levels +D0694560 0BD508FD +C4C2F5C3 B20CA10D +E98C99A4 4E623990 +Unlock Cheat Codes +Instant Weapon Recharge +BBC0C90B 94D21380 +Infinite Stealth +8CA10254 0218E2C8 +No Gun Overload +1DC8A526 7EE0CEAE +Mega Guns +3A7FE997 454307FA +Super After Burner +5C8F2184 34243C96 +Unlock All Missions +33576547 7602F359 +IC Forces Plane Pack 1 +F26DDE4F 50B6AF94 +IC Forces Plane Pack 2 +614D9C68 CFC15748 +IC Forces Plane Pack 3 +49B7561D 2C3DF848 +Enemy Plane Pack 1 +A7D46EF1 70B5781A +Enemy Plane Pack 2 +C206E8B8 8D603537 +Enemy Plane Pack 3 +81B00F5B 04E2BCCD +All Cheats +E71603A8 DB2C8455 +E98C99A4 4E623990 +Activate Cheat Codes +Instant Weapon Recharge +FA6DB01F E2977A7A +Infinite Stealth +8301E492 DE64EB9A +No Gun Overload +B21DF70E 82CC2624 +Mega Guns +C9EB488A 4A04C5D2 +Super After Burner +3CB8837E D7C7AF4A +All Cheats +F4160AC0 4997A6DE +3CB8837E D7C7AF4A +Unlock Level Codes +Lord Roberts Island 1-Welcome To Paradise +A65B9F8A 78544BD2 +Lord Roberts Island 1-The Attack Continues +1DBF9E8C C8A8274F +Lord Roberts Island 1-Search & Destroy +06C03E79 7B6635AD +Vertana 2-Smuggler's Run +73324A77 88F538B8 +Vertana 2-Smuggler's Demise +4FA30A90 4C8C8B35 +Vertana 2-Reprisals +815D1402 37412604 +Vertana 2-The Payback +99F22947 0F25385A +Vertana 2-Decapitation Strike +03EC91D7 CD28B482 +Antarctica 3-The Flash Point +471F1998 EDA80D2C +Antarctica 3-Operation Bear Trap +032E720D A8C5350C +Antarctica 3-Pillars of Fire +8C97B30A F07F42EF +Antarctica 3-The Brawl +74B0737C 8D40152D +Antarctica 3-Anvil & Hammer +44C676B0 BF5F5A64 +Kamcha 4-Sunburn Alley +008558E8 7FA49DD4 +Kamcha 4-Shark's Den +1142D963 6886BDC1 +Kamcha 4-To Rule The Sky +4F0B35C0 BB0F075D +Kamcha 4-Cargo of Death +8D484292 AF97F682 +Kamcha 4-The Final Countdown +3DFC3B08 41007925 +All Levels +9CE8238E A481D111 +AC3857DB A71F3A9C +E98C99A4 4E623990 diff --git a/test/cbc/heatseeker.ok b/test/cbc/heatseeker.ok new file mode 100644 index 0000000..e69de29 diff --git a/test/cbc/heatseeker.txt b/test/cbc/heatseeker.txt new file mode 100644 index 0000000..0d275d0 --- /dev/null +++ b/test/cbc/heatseeker.txt @@ -0,0 +1,94 @@ +"Heatseeker" +Enable Code (Must Be On) +90326BF0 00832021 +Disable FMV +2032D6B0 24020001 +Debug Menu +00A3541A 00000000 +Unlock All Planes/Bonus Levels +20A35BA0 01010100 +40A35C9C 000F0001 +01010101 00000000 +Unlock Cheat Codes +Instant Weapon Recharge +00A35CD8 00000001 +Infinite Stealth +00A35CD9 00000001 +No Gun Overload +00A35CDA 00000001 +Mega Guns +00A35CDB 00000001 +Super After Burner +00A35CDC 00000001 +Unlock All Missions +00A35CDD 00000001 +IC Forces Plane Pack 1 +00A35CDE 00000001 +IC Forces Plane Pack 2 +00A35CDF 00000001 +IC Forces Plane Pack 3 +00A35CE0 00000001 +Enemy Plane Pack 1 +00A35CE1 00000001 +Enemy Plane Pack 2 +00A35CE2 00000001 +Enemy Plane Pack 3 +00A35CE3 00000001 +All Cheats +40A35CD8 00030001 +01010101 00000000 +Activate Cheat Codes +Instant Weapon Recharge +00A3540C 00000001 +Infinite Stealth +00A3540D 00000001 +No Gun Overload +00A3540E 00000001 +Mega Guns +00A3540F 00000001 +Super After Burner +00A35410 00000001 +All Cheats +20A3540C 01010101 +00A35410 00000001 +Unlock Level Codes +Lord Roberts Island 1-Welcome To Paradise +00A35BA6 00000001 +Lord Roberts Island 1-The Attack Continues +00A35BA7 00000001 +Lord Roberts Island 1-Search & Destroy +00A35BA8 00000001 +Vertana 2-Smuggler's Run +00A35BA9 00000001 +Vertana 2-Smuggler's Demise +00A35BAA 00000001 +Vertana 2-Reprisals +00A35BAB 00000001 +Vertana 2-The Payback +00A35BAC 00000001 +Vertana 2-Decapitation Strike +00A35BAD 00000001 +Antarctica 3-The Flash Point +00A35BAE 00000001 +Antarctica 3-Operation Bear Trap +00A35BAF 00000001 +Antarctica 3-Pillars of Fire +00A35BB0 00000001 +Antarctica 3-The Brawl +00A35BB1 00000001 +Antarctica 3-Anvil & Hammer +00A35BB2 00000001 +Kamcha 4-Sunburn Alley +00A35BB3 00000001 +Kamcha 4-Shark's Den +00A35BB4 00000001 +Kamcha 4-To Rule The Sky +00A35BB5 00000001 +Kamcha 4-Cargo of Death +00A35BB6 00000001 +Kamcha 4-The Final Countdown +00A35BB7 00000001 +All Levels +10A35BA6 00000101 +40A35BA8 00040001 +01010101 00000000