diff options
43 files changed, 2247 insertions, 88 deletions
@@ -1,3 +1,4 @@ compile_commands.json sample_data/ analyzer/headers/server.h +analyzer/csv/ @@ -1,16 +1,44 @@ +CC=gcc +CFLAGS=-Wall -iquote ./analyzer -O2 -flto -s + global: compile sudo mv branal /usr/bin local: compile mv branal ${HOME}/.local/bin -compile: utils capture - gcc *.o main.c -o branal +debug: CFLAGS=-iquote ./analyzer -Og -g +debug: global + +compile: csv_table_gen main_files structures utils parser + $(CC) $(CFLAGS) -lpcap *.o main.c -o branal rm *.o +csv_table_gen: + $(CC) $(CFLAGS) csv_table_gen.c -o csv_table_gen + ./csv_table_gen + rm csv_table_gen + $(CC) $(CFLAGS) -c analyzer/csv/*.c + +main_files: + $(CC) $(CFLAGS) -c analyzer/commands.c + $(CC) $(CFLAGS) -c analyzer/capture.c + $(CC) $(CFLAGS) -c analyzer/analyzer.c + $(CC) $(CFLAGS) -c analyzer/render.c + +structures: + $(CC) $(CFLAGS) -c analyzer/structures/hashmap.c + $(CC) $(CFLAGS) -c analyzer/structures/heap.c + utils: - gcc -c analyzer/utils/fs.c - gcc -c analyzer/utils/time.c + $(CC) $(CFLAGS) -c analyzer/utils/fs.c + $(CC) $(CFLAGS) -c analyzer/utils/time.c + $(CC) $(CFLAGS) -c analyzer/utils/tcp.c + $(CC) $(CFLAGS) -c analyzer/utils/tcp_seq_math.c + $(CC) $(CFLAGS) -c analyzer/utils/roman.c + $(CC) $(CFLAGS) -c analyzer/utils/memory.c -capture: - gcc -c analyzer/capture.c +parser: + $(CC) $(CFLAGS) -c analyzer/parser/parser.c + $(CC) $(CFLAGS) -c analyzer/parser/structs.c + $(CC) $(CFLAGS) -c analyzer/parser/fight_results/fight_results.c @@ -6,10 +6,11 @@ Unless you value FOSS or have troubles setting up BrokenHUD on your linux system ## Features - Capturing packets sent by the game and saving them to a file for later use - -### Planned features - Analyzer for the `.pcap` files containing captured packets: - List of fight results in the given file with rewards gained for each player + +### Planned features +- File analysis: - Total gained rewards in all fights in the given file - Live analyzer parsing packets in real time: - Results of the last fight @@ -80,20 +81,20 @@ branal (l | live) ``` It will open the live analyzer in your terminal. -### File analyzer (NOT IMPLEMENTED) -To analize a `.pcap` file, use: +### File analyzer +To analize `.pcap` files, use: ``` -branal (f | file) <file> +branal (f | file) <file...> ``` -It will perform analysis of the specified `<file>`. +It will perform analysis of the specified file(s). ### Capturing packets To just capture packets from the server, use: ``` branal (c | capture) [file] ``` -It will start capturing packets to the specified `[file]`. -If `[file]` is not specified, it will capture them to the default location (`~/.local/share/branal/captures`). +It will start capturing packets to the specified file. +If file is not specified, it will capture them to the default location (`~/.local/share/branal/captures`). ### Help You can display help using: diff --git a/analyzer/analyzer.c b/analyzer/analyzer.c new file mode 100644 index 0000000..aa4a645 --- /dev/null +++ b/analyzer/analyzer.c @@ -0,0 +1,72 @@ +#include "analyzer.h" + +#include <stdlib.h> +#include <string.h> + +#include "parser/parser.h" +#include "render.h" +#include "utils/tcp.h" + +// loop through packets received from pcap_handle and analyze the TCP stream +void analyzer_loop( + TCPStream *stream, pcap_t *pcap_handle, void *state, void (*parsed_data_handler)(void *state, ParsedData *parsed_data, size_t n_parsed_data) +) { + // read packets from handle + struct pcap_pkthdr *packet_data; + const byte_t *packet; + while (pcap_next_ex(pcap_handle, &packet_data, &packet) == 1) { + // get new segment + TCPSegment segment = get_segment_from_packet(packet, packet_data); + + // handle the new segment if it's correct one and has data in it (ignore SYN) + if (segment.len > 0 && handle_segment(stream, segment)) { + // if it was added to the stream - parse it + size_t parsed_len; + size_t n_parsed_data; + ParsedData *parsed_data = parse_stream(stream, &parsed_len, &n_parsed_data); + + // if some bytes were parsed - remove them from the stream + if (parsed_len > 0) + remove_parsed_data(stream, parsed_len); + + // if there's parsed data - call the parsed data handler + if (n_parsed_data > 0) + parsed_data_handler(state, parsed_data, n_parsed_data); + + // free the entire parsed data memory + free_parsed_data(parsed_data, n_parsed_data); + } + } +} + +// file analyzer parsed data handler +void file_parsed_data_handler(void *state, ParsedData *parsed_data, size_t n_parsed_data) { + int *fights_count = state; + for (size_t i = 0; i < n_parsed_data; i++) { + switch (parsed_data[i].data_type) { + case TYPE_FIGHT_RESULTS: + printf("\nFIGHT %d:\n", ++*fights_count); + render_fight_results(*(FightResults *)parsed_data[i].data); + break; + } + } +} + +void analyze_file(char *path) { + // open the file + char errbuf[PCAP_ERRBUF_SIZE]; + pcap_t *handle = pcap_open_offline(path, errbuf); + + // handle errors + if (handle == NULL) { + fprintf(stderr, "Couldn't open the specified file:\n%s\n", errbuf); + return; + } + + // analyze the file + TCPStream stream = new_stream(); + int state = 0; + analyzer_loop(&stream, handle, &state, file_parsed_data_handler); + destroy_stream(stream); + pcap_close(handle); +} diff --git a/analyzer/analyzer.h b/analyzer/analyzer.h new file mode 100644 index 0000000..1b8d2f9 --- /dev/null +++ b/analyzer/analyzer.h @@ -0,0 +1,7 @@ +// analyzer.h +// functions for analyzing the packets received from the game server + +// analyze file +// params: +// - path - path to the file to analyze +extern void analyze_file(char *path); diff --git a/analyzer/commands.c b/analyzer/commands.c new file mode 100644 index 0000000..912bdf0 --- /dev/null +++ b/analyzer/commands.c @@ -0,0 +1,98 @@ +#include "commands.h" + +#include <stdio.h> +#include <string.h> + +#include "analyzer.h" +#include "capture.h" +#include "utils/memory.h" + +#define PROGRAM_NAME "branal" +#define INDENT " " +#define MAX_COMMAND_LENGTH 100 + +// calculate the longest command length to align explenations +int longest_command_length() { + int longest_len = 0; + for (size_t i = 0; i < commands_count; i++) { + Command cmd = commands[i]; + int len = strlen(PROGRAM_NAME); + len += strlen(" ( | ) "); + len += strlen(cmd.short_name) + strlen(cmd.name); + len += strlen(cmd.args_str) == 0 ? 0 : strlen(cmd.args_str) + 1; + if (len > longest_len) + longest_len = len; + } + return longest_len; +} + +// help command handler +int print_help(int argc, char *argv[]) { + int len = longest_command_length(); + + printf("A bare-bones analyzer for the game Broken Ranks\n"); + printf("\nUsage:\n"); + printf(INDENT "%-*s- same as below\n", len, PROGRAM_NAME); + + for (size_t i = 0; i < commands_count; i++) { + Command cmd = commands[i]; + + char buffer[MAX_COMMAND_LENGTH]; + sprintf(buffer, PROGRAM_NAME " (%s | %s) ", cmd.short_name, cmd.name); + if (strlen(cmd.args_str)) + strcat(buffer, cmd.args_str); + + printf(INDENT "%-*s- %s\n", len, buffer, cmd.explanation[0]); + for (size_t j = 1; j < cmd.explanation_lines; j++) + printf(INDENT "%*s%s\n", len + 2, "", cmd.explanation[j]); + } + + return 0; +} + +// live analyzer command handler +int live_anal(int argc, char *argv[]) { + // TODO: implement + printf("Not implemented\n"); + return 1; +} + +// capturing mode command handler +int capture(int argc, char *argv[]) { + capturing_mode(argc >= 3 ? argv[2] : NULL); + return 0; +} + +// file analysis command handler +int file_anal(int argc, char *argv[]) { + if (argc < 3) { + printf("You need to provide a file (or files) to analyze"); + return 1; + } + + for (size_t i = 2; i < argc; i++) { + printf("FILE: %s\n", argv[i]); + analyze_file(argv[i]); + printf("\n"); + } + + return 0; +} + +const Command help_command = {"help", "h", "", {"display this help"}, 1, print_help}; +const Command commands[] = { + {"live", "l", "", {"start live analysis"}, 1, live_anal}, + {"capture", "c", "[file]", {"start capturing packers to file", "If file isn't provided - captures to: $HOME/.local/share/branal/captures"}, 2, capture}, + {"file", "f", "<file...>", {"analyze the specified file(s)"}, 1, file_anal}, + help_command +}; +const size_t commands_count = ARR_LEN(commands); + +int is_command(Command command, char *str) { + return !strcmp(command.short_name, str) || !strcmp(command.name, str); +} + +void command_not_recognized() { + printf("Command not recognized\n"); + printf("Use \"%s (%s | %s)\" for help\n", PROGRAM_NAME, help_command.short_name, help_command.name); +} diff --git a/analyzer/commands.h b/analyzer/commands.h new file mode 100644 index 0000000..f53a140 --- /dev/null +++ b/analyzer/commands.h @@ -0,0 +1,30 @@ +// commands.h +// commands to use in the program + +#include <stddef.h> + +#define MAX_EXPLANATION_LINES 3 + +typedef struct { + char *name; + char *short_name; + char *args_str; + char *explanation[MAX_EXPLANATION_LINES]; + size_t explanation_lines; + int (*handler)(int argc, char *argv[]); +} Command; + +// all commands of the program +extern const Command commands[]; +extern const size_t commands_count; + +// check if the given command is represented by the given string +// params: +// - command - potentailly represented command +// - str - potentially representing string +// returns: +// 1 if command is represneted by string, 0 if not +extern int is_command(Command command, char *str); + +// print information about help when program didn't recognize command +extern void command_not_recognized(); diff --git a/analyzer/parser/fight_results/drif.h b/analyzer/parser/fight_results/drif.h new file mode 100644 index 0000000..1f8b3e2 --- /dev/null +++ b/analyzer/parser/fight_results/drif.h @@ -0,0 +1,52 @@ +// parser/fight_results/drif.h +// Drif generic struct data + +#include "parser/structs.h" + +// Drif structure: +// field (SEPARATOR_DRIF_FIELD field)* +// fields are defined in order in the Drif struct +#define SEPARATOR_DRIF "???" // TODO: change it to correct one which idk at this moment +#define SEPARATOR_DRIF_FIELD "," + +typedef struct { + char *field1; // just says "drif" + int field2; + int size; // 1 - sub, 2 - bi, 3 - magni, 4 - arcy + char *field4; + int field5; + int id; // id of orb type + float value1; // value1, value2 (in %) - one is start value, the other is value per lvl (both are the same) + float value2; // value1, value2 (in %) - one is start value, the other is value per lvl (both are the same) + int field9; + int field10; + char *field11; + char *size_str; // sub - small, medium - bi, big - magni, ??? - arcy + int field13; + int power; // how much space it takes + int field15; + int field16; + char *name; // <size>drif <name> + char *field18; +} Drif; + +static const StructField drif_fields[] = { + {STRING, offsetof(Drif, field1)}, + {INT, offsetof(Drif, field2)}, + {INT, offsetof(Drif, size)}, + {STRING, offsetof(Drif, field4)}, + {INT, offsetof(Drif, field5)}, + {INT, offsetof(Drif, id)}, + {FLOAT, offsetof(Drif, value1)}, + {FLOAT, offsetof(Drif, value2)}, + {INT, offsetof(Drif, field9)}, + {INT, offsetof(Drif, field10)}, + {STRING, offsetof(Drif, field11)}, + {STRING, offsetof(Drif, size_str)}, + {INT, offsetof(Drif, field13)}, + {INT, offsetof(Drif, power)}, + {INT, offsetof(Drif, field15)}, + {INT, offsetof(Drif, field16)}, + {STRING, offsetof(Drif, name)}, + {STRING, offsetof(Drif, field18)}, +}; diff --git a/analyzer/parser/fight_results/entity.h b/analyzer/parser/fight_results/entity.h new file mode 100644 index 0000000..8e082ec --- /dev/null +++ b/analyzer/parser/fight_results/entity.h @@ -0,0 +1,67 @@ +// parser/fight_results/entity.h +// EntityResults generic struct data + +#include "parser/structs.h" + +// EntityResults structure: +// field (SEPARATOR_ENTITY_FIELD field)* +// fields are defined in order in the EntityResults struct +#define SEPARATOR_ENTITY "[--]" +#define SEPARATOR_ENTITY_FIELD "&" + +// values of field "team" in EntityResults +#define TEAM_FRIEND 1 +#define TEAM_ENEMY 2 + +typedef struct { + int team; // friend or enemy + char *name; + int exp; + float field4; + int gold; + float field6; + char *field7; + char *gear; // rares, sets, syngs, orbs + int field9; + char *items; // regular items + int field11; + int field12; + float field13; + float field14; // some multiplier most likely exp/gold/psycho/something else idk + float field15; + int level; + double field17; + float field18; + int field19; + int field20; + float field21; + float field22; + int field23; + int field24; + int psycho; + char *drifs; + int splinters; + int field28; + char *field29; + int field30; + int field31; + int field32; + int field33; + char *field34; + char *saturation; +} EntityResults; + +static const StructField entity_fields[] = { + {INT, offsetof(EntityResults, team)}, {STRING, offsetof(EntityResults, name)}, {INT, offsetof(EntityResults, exp)}, + {FLOAT, offsetof(EntityResults, field4)}, {INT, offsetof(EntityResults, gold)}, {FLOAT, offsetof(EntityResults, field6)}, + {STRING, offsetof(EntityResults, field7)}, {STRING, offsetof(EntityResults, gear)}, {INT, offsetof(EntityResults, field9)}, + {STRING, offsetof(EntityResults, items)}, {INT, offsetof(EntityResults, field11)}, {INT, offsetof(EntityResults, field12)}, + {FLOAT, offsetof(EntityResults, field13)}, {FLOAT, offsetof(EntityResults, field14)}, {FLOAT, offsetof(EntityResults, field15)}, + {INT, offsetof(EntityResults, level)}, {DOUBLE, offsetof(EntityResults, field17)}, {FLOAT, offsetof(EntityResults, field18)}, + {INT, offsetof(EntityResults, field19)}, {INT, offsetof(EntityResults, field20)}, {FLOAT, offsetof(EntityResults, field21)}, + {FLOAT, offsetof(EntityResults, field22)}, {INT, offsetof(EntityResults, field23)}, {INT, offsetof(EntityResults, field24)}, + {INT, offsetof(EntityResults, psycho)}, {STRING, offsetof(EntityResults, drifs)}, {INT, offsetof(EntityResults, splinters)}, + {INT, offsetof(EntityResults, field28)}, {STRING, offsetof(EntityResults, field29)}, {INT, offsetof(EntityResults, field30)}, + {INT, offsetof(EntityResults, field31)}, {INT, offsetof(EntityResults, field32)}, {INT, offsetof(EntityResults, field33)}, + {STRING, offsetof(EntityResults, field34)}, {STRING, offsetof(EntityResults, saturation)}, +}; diff --git a/analyzer/parser/fight_results/fight_results.c b/analyzer/parser/fight_results/fight_results.c new file mode 100644 index 0000000..efc4b36 --- /dev/null +++ b/analyzer/parser/fight_results/fight_results.c @@ -0,0 +1,260 @@ +#include "parser/fight_results/fight_results.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "csv/id_to_artifact_size.h" +#include "csv/id_to_item_name.h" +#include "csv/id_to_orb_name.h" +#include "csv/id_to_saturation_type.h" +#include "parser/fight_results/drif.h" +#include "parser/fight_results/entity.h" +#include "parser/fight_results/gear.h" +#include "parser/fight_results/gear_info.h" +#include "parser/fight_results/orb.h" +#include "parser/structs.h" +#include "utils/memory.h" +#include "utils/roman.h" + +// function to cleanup fight results to be used in ParsedData struct +void cleanup_fight_results(void *data) { + FightResults *fight_results = data; + for (size_t i = 0; i < fight_results->num_friendly; i++) { + FriendlyResults friendly = fight_results->friendly_results[i]; + free(friendly.name); + for (size_t j = 0; j < friendly.num_items; j++) + free(friendly.items[j].data); + free(friendly.items); + } + for (size_t i = 0; i < fight_results->num_enemy; i++) { + free(fight_results->enemy_results[i].name); + } + free(fight_results->friendly_results); + free(fight_results->enemy_results); +} + +byte_t *find_fight_results(TCPStream *stream, int offset, int *length) { + byte_t *start = memmem(stream->data + offset, stream->len - offset, FIGHT_RESULTS_START, strlen(FIGHT_RESULTS_START)); + byte_t *end = NULL; + if (start) + end = memmem(start, stream->len - (start - stream->data), FIGHT_RESULTS_END, strlen(FIGHT_RESULTS_END)); + *length = end ? end - start + strlen(FIGHT_RESULTS_END) : -1; + return start; +} + +void get_stars(char *buffer, int incr_above_b1) { + char star_types[] = {'B', 'S', 'G'}; + sprintf(buffer, " (%c%d)", star_types[incr_above_b1 / 3], incr_above_b1 % 3 + 1); +} + +void add_gear(FriendlyResults *results, size_t *items_capacity, char *gears_str) { + if (strlen(gears_str) == 0) + return; + + char *gears_separator; + do { + gears_separator = strstr(gears_str, SEPARATOR_GEAR); + size_t len = gears_separator ? gears_separator - gears_str : strlen(gears_str); + Gear gear = {0}; + fill_struct(&gear, gear_fields, ARR_LEN(gear_fields), gears_str, len, SEPARATOR_GEAR_FIELD); + + // if there are still gears - modify gears_str pointer + if (gears_separator) + gears_str = gears_separator + strlen(SEPARATOR_GEAR); + + // get gear info for rank, name and stars / syng level + GearInfo gear_info = {0}; + fill_struct(&gear_info, gear_info_fields, ARR_LEN(gear_info_fields), gear.info, strlen(gear.info), SEPARATOR_GEAR_INFO_FIELD); + + // create the buffer of information about gear + char buffer[SINGLE_ITEM_BUFFER_LEN]; + char roman[5]; + to_roman(roman, gear_info.rank); + sprintf(buffer, "[%s] %s", roman, gear_info.name); + switch (gear.type) { + case TYPE_SYNG_NORMAL: + // atoi should be fine, since syng_tier starts from 1 for syngs + if (atoi(gear.syng_tier)) { + char lvl[7]; + sprintf(lvl, " (%d)", gear_info.lvl); + strcat(buffer, lvl); + } + break; + default: { + char stars[6]; + get_stars(stars, gear_info.stars); + strcat(buffer, stars); + break; + } + } + + // add gear with specific type + Item item = {.data = strdup(buffer)}; + switch (gear.type) { + case TYPE_SYNG_NORMAL: + // atoi should be fine, since syng_tier starts from 1 for syngs + item.type = atoi(gear.syng_tier) ? SYNG : NORMAL; + break; + case TYPE_SET: + item.type = SET; + break; + case TYPE_RARE: + item.type = RARE; + break; + default: + item.type = EPIC; + break; + } + results->items = array_append(results->items, sizeof(Item), &results->num_items, items_capacity, &item); + + cleanup_struct(&gear_info, gear_info_fields, ARR_LEN(gear_info_fields)); + + // add orb to items if it exists + if (strlen(gear.orb)) { + Orb orb = {0}; + fill_struct(&orb, orb_fields, ARR_LEN(orb_fields), gear.orb, strlen(gear.orb), SEPARATOR_ORB_FIELD); + + char buffer[SINGLE_ITEM_BUFFER_LEN]; + sprintf(buffer, "%sorb %s", get_artifact_size_from_id(orb.size), get_orb_name_from_id(orb.id)); + + Item item = {.data = strdup(buffer), .type = ORB}; + results->items = array_append(results->items, sizeof(Item), &results->num_items, items_capacity, &item); + + cleanup_struct(&orb, orb_fields, ARR_LEN(orb_fields)); + } + + cleanup_struct(&gear, gear_fields, ARR_LEN(gear_fields)); + } while (gears_separator); +} + +void add_drifs(FriendlyResults *results, size_t *items_capacity, char *drifs_str) { + if (strlen(drifs_str) == 0) + return; + + char *drifs_separator; + do { + drifs_separator = strstr(drifs_str, SEPARATOR_DRIF); + size_t len = drifs_separator ? drifs_separator - drifs_str : strlen(drifs_str); + Drif drif = {0}; + fill_struct(&drif, drif_fields, ARR_LEN(drif_fields), drifs_str, len, SEPARATOR_DRIF_FIELD); + + // if there are still drifs - modify drifs_str pointer + if (drifs_separator) + drifs_str = drifs_separator + strlen(SEPARATOR_DRIF); + + Item item = {.data = strdup(drif.name), .type = DRIF}; + results->items = array_append(results->items, sizeof(Item), &results->num_items, items_capacity, &item); + + cleanup_struct(&drif, drif_fields, ARR_LEN(drif_fields)); + } while (drifs_separator); +} + +void add_items(FriendlyResults *results, size_t *items_capacity, char *items_str) { + if (strlen(items_str) == 0) + return; + + char *items_separator; + do { + char buffer[SINGLE_ITEM_BUFFER_LEN]; + items_separator = strstr(items_str, SEPARATOR_ITEMS); + + int item_id, item_count; + sscanf(items_str, "%d,%d", &item_id, &item_count); + sprintf(buffer, "%dx %s", item_count, get_item_name_from_id(item_id)); + + // if there are still items - modify items_str pointer + if (items_separator) + items_str = items_separator + strlen(SEPARATOR_ITEMS); + + Item item = {.data = strdup(buffer), .type = NORMAL}; + results->items = array_append(results->items, sizeof(Item), &results->num_items, items_capacity, &item); + } while (items_separator); +} + +// parse EntityResults to FriendlyResults +FriendlyResults parse_to_friendly(EntityResults entity_results) { + FriendlyResults results = { + .name = strdup(entity_results.name), + .exp = entity_results.exp, + .gold = entity_results.gold, + .level = entity_results.level, + .psycho = entity_results.psycho, + .splinters = entity_results.splinters, + .num_items = 0, + .saturation = 0 + }; + + // create items array and add to it gear, drifs and items + results.items = safe_malloc(INIT_ITEMS_CAPACITY * sizeof(Item)); + size_t items_cap = INIT_ITEMS_CAPACITY; + add_gear(&results, &items_cap, entity_results.gear); + add_drifs(&results, &items_cap, entity_results.drifs); + add_items(&results, &items_cap, entity_results.items); + + if (strlen(entity_results.saturation) > 0) { + int saturation_id; + sscanf(entity_results.saturation, "%d,%d", &saturation_id, &results.saturation); + results.saturation_type = get_saturation_type_from_id(saturation_id); + } + + return results; +} + +// parse EntityResults to EnemyResults +EnemyResults parse_to_enemy(EntityResults entity_results) { + EnemyResults results = {.name = strdup(entity_results.name), .level = entity_results.level}; + return results; +} + +ParsedData parse_fight_results(byte_t *data, size_t length) { + ParsedData parsed_data = {.data_type = TYPE_FIGHT_RESULTS, .data_cleanup = cleanup_fight_results}; + + // ignore start and end indicators + data += strlen(FIGHT_RESULTS_START); + length -= strlen(FIGHT_RESULTS_START) + strlen(FIGHT_RESULTS_END); + + FightResults *results = safe_malloc(sizeof(FightResults)); + + results->friendly_results = safe_malloc(INIT_ENTITIES_PER_SIDE * sizeof(FriendlyResults)); + results->num_friendly = 0; + size_t friendly_cap = INIT_ENTITIES_PER_SIDE; + + results->enemy_results = safe_malloc(INIT_ENTITIES_PER_SIDE * sizeof(EnemyResults)); + results->num_enemy = 0; + size_t enemy_cap = INIT_ENTITIES_PER_SIDE; + + byte_t *entity_separator; + do { + // get generic entity results + entity_separator = memmem(data, length, SEPARATOR_ENTITY, strlen(SEPARATOR_ENTITY)); + size_t len = entity_separator ? entity_separator - data : length; + EntityResults entity_results = {0}; + fill_struct(&entity_results, entity_fields, ARR_LEN(entity_fields), (char *)data, len, SEPARATOR_ENTITY_FIELD); + + // depending on team - parse it into friendly or enemy results + switch (entity_results.team) { + case TEAM_FRIEND: { + FriendlyResults res = parse_to_friendly(entity_results); + results->friendly_results = array_append(results->friendly_results, sizeof(FriendlyResults), &results->num_friendly, &friendly_cap, &res); + break; + } + case TEAM_ENEMY: { + EnemyResults res = parse_to_enemy(entity_results); + results->enemy_results = array_append(results->enemy_results, sizeof(EnemyResults), &results->num_enemy, &enemy_cap, &res); + break; + } + } + + cleanup_struct(&entity_results, entity_fields, ARR_LEN(entity_fields)); + + // if there are still entities - modify data pointer and length + if (entity_separator) { + length -= entity_separator - data + strlen(SEPARATOR_ENTITY); + data = entity_separator + strlen(SEPARATOR_ENTITY); + } + } while (entity_separator); + + parsed_data.data = results; + return parsed_data; +} diff --git a/analyzer/parser/fight_results/fight_results.h b/analyzer/parser/fight_results/fight_results.h new file mode 100644 index 0000000..ec90394 --- /dev/null +++ b/analyzer/parser/fight_results/fight_results.h @@ -0,0 +1,60 @@ +// parser/fight_results.h +// utilities for parsing the fight results data + +#include "parser/parser_defs.h" +#include "utils/tcp.h" + +#ifndef FIGHT_RESULTS_H +#define FIGHT_RESULTS_H + +// Payload structure: +// FIGHT_RESULTS_START EntityResults (SEPARATOR_ENTITY EntityResults)* FIGHT_RESULTS_END +#define FIGHT_RESULTS_START "3;19;" +#define FIGHT_RESULTS_END "[--];" + +// items parsing info +#define INIT_ITEMS_CAPACITY 8 +#define SINGLE_ITEM_BUFFER_LEN 128 +#define SEPARATOR_ITEMS "[-]" + +// Item struct - for keeping item string and type +typedef enum { NORMAL, DRIF, ORB, SYNG, SET, RARE, EPIC } ItemType; +typedef struct { + char *data; + ItemType type; +} Item; + +// fight results data for friendly +typedef struct { + char *name; + int exp; + int gold; + Item *items; + size_t num_items; + int level; + int psycho; + int splinters; + const char *saturation_type; + int saturation; +} FriendlyResults; + +// fight results data for enemy +typedef struct { + char *name; + int level; +} EnemyResults; + +#define INIT_ENTITIES_PER_SIDE 6 + +// entire fight resutls +typedef struct { + size_t num_friendly; + FriendlyResults *friendly_results; + size_t num_enemy; + EnemyResults *enemy_results; +} FightResults; + +extern find_function_t find_fight_results; +extern parse_function_t parse_fight_results; + +#endif diff --git a/analyzer/parser/fight_results/gear.h b/analyzer/parser/fight_results/gear.h new file mode 100644 index 0000000..66f52f3 --- /dev/null +++ b/analyzer/parser/fight_results/gear.h @@ -0,0 +1,45 @@ +// parser/fight_results/gear.h +// Gear generic struct data + +#include "parser/structs.h" + +// Gear structure: +// field (SEPARATOR_GEAR_FIELD field)* +// fields are defined in order in the Gear struct +#define SEPARATOR_GEAR "???" // TODO: change it to correct one which idk at this moment +#define SEPARATOR_GEAR_FIELD "$" + +// values of field "type" in Gear +#define TYPE_SYNG_NORMAL 0 +#define TYPE_SET 1 +#define TYPE_RARE 2 + +typedef struct { + char *stats; // strength, dexterity, power, knowledge, hp, mana, stamina + char *resistances; // slashing, crushing, piercing, fire, cold, energy, mental + char *info; // GearInfo struct data + char *reqs; // requirements (str, dex, pow, know), weight, something else + char *field5; + char *psycho; // psycho effects (id,%) for syngs + char *field7; + char *field8; + char *field9; + char *syng_tier; // lvl 20 - 1, lvl 30 - 2, etc..., null for non-syngs + int field11; + int field12; + int field13; + char *field14; + int type; // 0 - normal item or syng, 1 - set, 2 - rare + int field16; // 10 for rare, -1 for others ??? + char *orb; // orb info + char *field18; + char *field19; +} Gear; + +static const StructField gear_fields[] = { + {STRING, offsetof(Gear, stats)}, {STRING, offsetof(Gear, resistances)}, {STRING, offsetof(Gear, info)}, {STRING, offsetof(Gear, reqs)}, + {STRING, offsetof(Gear, field5)}, {STRING, offsetof(Gear, psycho)}, {STRING, offsetof(Gear, field7)}, {STRING, offsetof(Gear, field8)}, + {STRING, offsetof(Gear, field9)}, {STRING, offsetof(Gear, syng_tier)}, {INT, offsetof(Gear, field11)}, {INT, offsetof(Gear, field12)}, + {INT, offsetof(Gear, field13)}, {STRING, offsetof(Gear, field14)}, {INT, offsetof(Gear, type)}, {INT, offsetof(Gear, field16)}, + {STRING, offsetof(Gear, orb)}, {STRING, offsetof(Gear, field18)}, {STRING, offsetof(Gear, field19)}, +}; diff --git a/analyzer/parser/fight_results/gear_info.h b/analyzer/parser/fight_results/gear_info.h new file mode 100644 index 0000000..f0cf83a --- /dev/null +++ b/analyzer/parser/fight_results/gear_info.h @@ -0,0 +1,44 @@ +// parser/fight_results/gear_info.h +// GearInfo generic struct data + +#include "parser/structs.h" + +// GearInfo structure: +// field (SEPARATOR_GEAR_INFO_FIELD field)* +// fields are defined in order in the GearInfo struct +#define SEPARATOR_GEAR_INFO_FIELD "," + +typedef struct { + char *name; // item name + int field2; + int lvl; // level requirement + int price; // gold price value + int field5; + int field6; + int field7; + int rank; // I-XII + int field9; + int field10; + int field11; + int durability; // in % + char *field13; + int field14; + int field15; + float charges; // charge count + char *field17; + int field18; + int stars; // incrustation level: 0 - b1, 1 - b2, ... (-100 for syngs) + int field20; + int field21; + int field22; + int field23; +} GearInfo; + +static const StructField gear_info_fields[] = { + {STRING, offsetof(GearInfo, name)}, {INT, offsetof(GearInfo, field2)}, {INT, offsetof(GearInfo, lvl)}, {INT, offsetof(GearInfo, price)}, + {INT, offsetof(GearInfo, field5)}, {INT, offsetof(GearInfo, field6)}, {INT, offsetof(GearInfo, field7)}, {INT, offsetof(GearInfo, rank)}, + {INT, offsetof(GearInfo, field9)}, {INT, offsetof(GearInfo, field10)}, {INT, offsetof(GearInfo, field11)}, {INT, offsetof(GearInfo, durability)}, + {STRING, offsetof(GearInfo, field13)}, {INT, offsetof(GearInfo, field14)}, {INT, offsetof(GearInfo, field15)}, {FLOAT, offsetof(GearInfo, charges)}, + {STRING, offsetof(GearInfo, field17)}, {INT, offsetof(GearInfo, field18)}, {INT, offsetof(GearInfo, stars)}, {INT, offsetof(GearInfo, field20)}, + {INT, offsetof(GearInfo, field21)}, {INT, offsetof(GearInfo, field22)}, {INT, offsetof(GearInfo, field23)}, +}; diff --git a/analyzer/parser/fight_results/orb.h b/analyzer/parser/fight_results/orb.h new file mode 100644 index 0000000..dcf146f --- /dev/null +++ b/analyzer/parser/fight_results/orb.h @@ -0,0 +1,33 @@ +// parser/fight_results/orb.h +// Orb generic struct data + +#include "parser/structs.h" + +// Orb structure: +// field (SEPARATOR_ORB_FIELD field)* +// fields are defined in order in the Orb struct +#define SEPARATOR_ORB_FIELD "," + +typedef struct { + char *field1; // just says "orb" + int field2; + int field3; + char *field4; + int field5; + int id; // id of orb type + int size; // 1 - sub, 2 - bi, 3 - magni, 4 - arcy + float value; // value in % you gain from the orb + char *field9; +} Orb; + +static const StructField orb_fields[] = { + {STRING, offsetof(Orb, field1)}, + {INT, offsetof(Orb, field2)}, + {INT, offsetof(Orb, field3)}, + {STRING, offsetof(Orb, field4)}, + {INT, offsetof(Orb, field5)}, + {INT, offsetof(Orb, id)}, + {INT, offsetof(Orb, size)}, + {FLOAT, offsetof(Orb, value)}, + {STRING, offsetof(Orb, field9)}, +}; diff --git a/analyzer/parser/find_parse_functions.h b/analyzer/parser/find_parse_functions.h new file mode 100644 index 0000000..e2c2acb --- /dev/null +++ b/analyzer/parser/find_parse_functions.h @@ -0,0 +1,6 @@ +// parser/find_parse_functions.h +// array of find and parse functions for every result type + +#include "parser/fight_results/fight_results.h" + +FindParseFunctions all_find_parse_functions[] = {{find_fight_results, parse_fight_results}}; diff --git a/analyzer/parser/parser.c b/analyzer/parser/parser.c new file mode 100644 index 0000000..d00cd97 --- /dev/null +++ b/analyzer/parser/parser.c @@ -0,0 +1,64 @@ +#include "parser/parser.h" + +#include <stdlib.h> + +#include "parser/find_parse_functions.h" +#include "utils/memory.h" + +// find earliest data to parse in stream +byte_t *find_earliest_data(TCPStream *stream, int stream_offset, int *earliest_length, parse_function_t **earliest_parse_fun) { + byte_t *earliest_data = NULL; + for (size_t i = 0; i < ARR_LEN(all_find_parse_functions); i++) { + int length; + byte_t *data = all_find_parse_functions[i].find_fn(stream, stream_offset, &length); + // if it's the first found or earlier than earliest - save it + if (data && (!earliest_data || data < earliest_data)) { + earliest_data = data; + *earliest_length = length; + *earliest_parse_fun = all_find_parse_functions[i].parse_fn; + } + } + return earliest_data; +} + +ParsedData *parse_stream(TCPStream *stream, size_t *parsed_len, size_t *n_parsed_data) { + size_t parsed_data_capacity = INIT_PARSED_DATA_CAPACITY; + ParsedData *parsed_data = safe_malloc(parsed_data_capacity * sizeof(ParsedData)); + *n_parsed_data = 0; + + int stream_offset = 0; + byte_t *earliest_data; + do { + // find earliest data to parse + int earliest_length; + parse_function_t *earliest_parse_fun; + earliest_data = find_earliest_data(stream, stream_offset, &earliest_length, &earliest_parse_fun); + + // if start of data to parse was found, but not the end, then finish here + // set parsed_len, so that the beginning of that data is at the beginning of the stream + if (earliest_data && earliest_length == -1) { + *parsed_len = earliest_data - stream->data; + return parsed_data; + } + + // if data to parse was found, parse it and update the offset + if (earliest_data) { + ParsedData new_parsed = earliest_parse_fun(earliest_data, earliest_length); + parsed_data = array_append(parsed_data, sizeof(ParsedData), n_parsed_data, &parsed_data_capacity, &new_parsed); + stream_offset = earliest_data - stream->data + earliest_length; + } + } while (earliest_data); + + // if all was parsed - set parsed_len to stream_len and return the data + *parsed_len = stream->len; + return parsed_data; +} + +void free_parsed_data(ParsedData *parsed_data, size_t n_parsed_data) { + for (size_t i = 0; i < n_parsed_data; i++) { + if (parsed_data[i].data_cleanup) + parsed_data[i].data_cleanup(parsed_data[i].data); + free(parsed_data[i].data); + } + free(parsed_data); +} diff --git a/analyzer/parser/parser.h b/analyzer/parser/parser.h new file mode 100644 index 0000000..e7c46e8 --- /dev/null +++ b/analyzer/parser/parser.h @@ -0,0 +1,23 @@ +// parser/parser.h +// main parser file + +#include "parser/fight_results/fight_results.h" +#include "parser/parser_defs.h" +#include "utils/tcp.h" + +#define INIT_PARSED_DATA_CAPACITY 4 + +// parse a stream of TCP data +// params: +// - stream - the stream to parse +// - parsed_len - pointer to inform how much of the stream was parsed (not all if e.g. start of data is there, but the end isn't yet) +// - n_parsed_data - pointer to the count of parsed data that was retrieved from the stream +// returns: +// pointer to the array of parsed data +extern ParsedData *parse_stream(TCPStream *stream, size_t *parsed_len, size_t *n_parsed_data); + +// free the entire parsed data array from parse_stream function +// params: +// - parsed_data - parsed data to free +// - n_parsed_data - count of parsed data +extern void free_parsed_data(ParsedData *parsed_data, size_t n_parsed_data); diff --git a/analyzer/parser/parser_defs.h b/analyzer/parser/parser_defs.h new file mode 100644 index 0000000..75b0366 --- /dev/null +++ b/analyzer/parser/parser_defs.h @@ -0,0 +1,42 @@ +// parser/parser_defs.h +// useful definitions for parser files + +#include "utils/tcp.h" + +#ifndef PARSER_DEFS_H +#define PARSER_DEFS_H + +typedef enum { TYPE_FIGHT_RESULTS } ResultsEnum; + +// "generic" type for parsed data +typedef struct { + ResultsEnum data_type; // type of data represented by an enum + void *data; // pointer to the parsed data + void (*data_cleanup)(void *data); // function to cleanup internal allocations inside data + // if no internal allocations - this should be NULL +} ParsedData; + +// find the first results data of specific type in stream +// params: +// - stream - stream to find the data in +// - offset - offset to start finding data from +// - length - length of the data or -1 if there's no end yet +// returns: +// pointer to the beginning of data or NULL if there's none +typedef byte_t *find_function_t(TCPStream *stream, int offset, int *length); + +// parse given data of specifc type +// params: +// - data - data to parse +// - length - length of data +// returns: +// ParsedData struct including the parsed data +typedef ParsedData parse_function_t(byte_t *data, size_t length); + +// struct for holding the find and parse function for every result type +typedef struct { + find_function_t *find_fn; + parse_function_t *parse_fn; +} FindParseFunctions; + +#endif diff --git a/analyzer/parser/structs.c b/analyzer/parser/structs.c new file mode 100644 index 0000000..dedf0d1 --- /dev/null +++ b/analyzer/parser/structs.c @@ -0,0 +1,51 @@ +#include "parser/structs.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "utils/memory.h" + +void fill_struct(void *obj, const StructField *fields, size_t n_fields, const char *data, size_t data_len, const char *sep) { + for (size_t i = 0; i < n_fields; i++) { + char *next_sep = memmem(data, data_len, sep, strlen(sep)); + size_t len = next_sep ? next_sep - data : data_len; + + char *str = safe_malloc(len + 1); + memcpy(str, data, len); + str[len] = '\0'; + + // parse depending on variable type + void *p_field = (char *)obj + fields[i].offset; + switch (fields[i].type) { + case INT: + sscanf(str, "%d", (int *)p_field); + free(str); + break; + case FLOAT: + sscanf(str, "%f", (float *)p_field); + free(str); + break; + case DOUBLE: + sscanf(str, "%lf", (double *)p_field); + free(str); + break; + case STRING: + *(char **)p_field = str; + break; + } + + // update data pointer and length + if (next_sep) { + data_len -= next_sep - data + strlen(sep); + data = next_sep + strlen(sep); + } + } +} + +void cleanup_struct(void *obj, const StructField *fields, size_t n_fields) { + for (size_t i = 0; i < n_fields; i++) { + if (fields[i].type == STRING) + free(*(char **)((char *)obj + fields[i].offset)); + } +} diff --git a/analyzer/parser/structs.h b/analyzer/parser/structs.h new file mode 100644 index 0000000..023c19b --- /dev/null +++ b/analyzer/parser/structs.h @@ -0,0 +1,33 @@ +// parser/structs.h +// generic struct functionality for parsing + +#include <stddef.h> + +#ifndef PARSER_STRUCTS_H +#define PARSER_STRUCTS_H + +typedef enum { INT, FLOAT, DOUBLE, STRING } TypeEnum; + +typedef struct { + TypeEnum type; + size_t offset; +} StructField; + +// fill generic struct with given data +// params: +// - obj - pointer to the struct to fill +// - fields - field types and offsets of the struct +// - n_fields - count of fields in the struct +// - data - data to fill the struct with +// - data_len - length of the data +// - sep - separator seperating the fields in data +extern void fill_struct(void *obj, const StructField *fields, size_t n_fields, const char *data, size_t data_len, const char *sep); + +// cleanup allocations inside generic struct +// params: +// - obj - pointer to the struct to cleanup +// - fields - field types and offsets of the struct +// - n_fields - count of fields in the struct +extern void cleanup_struct(void *obj, const StructField *fields, size_t n_fields); + +#endif diff --git a/analyzer/render.c b/analyzer/render.c new file mode 100644 index 0000000..5c4b2d0 --- /dev/null +++ b/analyzer/render.c @@ -0,0 +1,162 @@ +#include "render.h" + +#include <stdio.h> +#include <string.h> + +// strlen counting utf-8 characters as one +size_t utf8_strlen(char *s) { + size_t len = 0; + while (*s) { + // if the byte is not a continuation byte (10xxxxxx), it's a new character + if ((*s & 0xC0) != 0x80) + len++; + s++; + } + return len; +} + +void print_row_separator() { + for (size_t i = 0; i < TABLE_LINE_WIDTH; i++) + printf("%c", TABLE_ROW_SEPARATOR_CHAR); + printf("\n"); +} + +// print string with given color +void print_color(char *str, int color) { + printf("\033[%dm%s\033[0m", color, str); +} + +void print_center(char *text, int color, size_t width, char padding_char) { + size_t post_char_count = (width - strlen(text)) / 2; + size_t pre_char_count = width - post_char_count - strlen(text); + + for (size_t i = 0; i < pre_char_count; i++) + printf("%c", padding_char); + + print_color(text, color); + + for (size_t i = 0; i < post_char_count; i++) + printf("%c", padding_char); +} + +void print_enemy_list(EnemyResults *results, size_t n_enemies) { + printf(TABLE_LINE_START); + size_t line_length = TABLE_LINE_START_WIDTH; + + for (size_t i = 0; i < n_enemies; i++) { + EnemyResults enemy = results[i]; + char enemy_buffer[TABLE_LINE_WIDTH + 1]; + sprintf(enemy_buffer, "%s (%d)%s", enemy.name, enemy.level, i < n_enemies - 1 ? ", " : ""); + + // break line if length would be too big + if (line_length + utf8_strlen(enemy_buffer) > TABLE_LINE_WIDTH - TABLE_LINE_END_WIDTH) { + printf("%*s\n", (int)(TABLE_LINE_WIDTH - line_length), TABLE_LINE_END); + printf(TABLE_LINE_START); + line_length = TABLE_LINE_START_WIDTH; + } + + printf("%s", enemy_buffer); + line_length += utf8_strlen(enemy_buffer); + } + + printf("%*s\n", (int)(TABLE_LINE_WIDTH - line_length), TABLE_LINE_END); +} + +void print_column_names() { + printf(TABLE_LINE_START); + for (size_t i = 0; i < ARR_LEN(columns); i++) { + print_center(columns[i].name, columns[i].color, columns[i].width, ' '); + printf(i < ARR_LEN(columns) - 1 ? TABLE_LINE_SEPARATOR : TABLE_LINE_END); + } + printf("\n"); +} + +int item_type_to_color(ItemType type) { + switch (type) { + case NORMAL: + return COLOR_WHITE; + case DRIF: + return COLOR_YELLOW; + case ORB: + return COLOR_GOLD; + case SYNG: + return COLOR_RED; + case SET: + return COLOR_GREEN; + case RARE: + return COLOR_AQUA; + case EPIC: + return COLOR_PURPLE; + } + return COLOR_WHITE; +} + +void print_player_rewards(FriendlyResults results) { + char buffers[ARR_LEN(columns)][TABLE_LINE_WIDTH]; + + sprintf(buffers[0], "%s (%d)", results.name, results.level); + sprintf(buffers[1], "%d", results.exp); + sprintf(buffers[2], "%d", results.gold); + + if (results.psycho > 0) + sprintf(buffers[3], "%d", results.psycho); + else + sprintf(buffers[3], "-"); + + if (results.saturation > 0) + sprintf(buffers[4], "%d (%s)", results.saturation, results.saturation_type); + else + sprintf(buffers[4], "-"); + + if (results.splinters > 0) + sprintf(buffers[5], "%d", results.splinters); + else + sprintf(buffers[5], "-"); + + printf(TABLE_LINE_START); + for (size_t i = 0; i < ARR_LEN(columns); i++) { + char to_color[columns[i].width + 1]; + sprintf(to_color, "%*s", (int)columns[i].width, buffers[i]); + print_color(to_color, columns[i].color); + printf(i < ARR_LEN(columns) - 1 ? TABLE_LINE_SEPARATOR : TABLE_LINE_END); + } + printf("\n"); + + if (results.num_items > 0) { + printf(TABLE_LINE_START); + print_center("ITEMS", COLOR_WHITE, TABLE_ITEMS_WIDTH, ' '); + printf(TABLE_LINE_END "\n" TABLE_LINE_START); + + size_t line_len = 0; + for (size_t i = 0; i < results.num_items; i++) { + char *sep = i < results.num_items - 1 ? ", " : ""; + size_t item_len = utf8_strlen(results.items[i].data) + strlen(sep); + if (line_len + item_len > TABLE_ITEMS_WIDTH) { + printf("%*s" TABLE_LINE_END "\n" TABLE_LINE_START, (int)(TABLE_ITEMS_WIDTH - line_len), ""); + line_len = 0; + } + print_color(results.items[i].data, item_type_to_color(results.items[i].type)); + printf("%s", sep); + line_len += item_len; + } + printf("%*s" TABLE_LINE_END "\n", (int)(TABLE_ITEMS_WIDTH - line_len), ""); + } +} + +void render_fight_results(FightResults results) { + print_center("ENEMIES", COLOR_RED, TABLE_LINE_WIDTH, TABLE_ROW_SEPARATOR_CHAR); + printf("\n"); + + print_enemy_list(results.enemy_results, results.num_enemy); + + print_center("PLAYER REWARDS", COLOR_GREEN, TABLE_LINE_WIDTH, TABLE_ROW_SEPARATOR_CHAR); + printf("\n"); + + print_column_names(); + print_row_separator(); + + for (size_t i = 0; i < results.num_friendly; i++) { + print_player_rewards(results.friendly_results[i]); + print_row_separator(); + } +} diff --git a/analyzer/render.h b/analyzer/render.h new file mode 100644 index 0000000..911ca76 --- /dev/null +++ b/analyzer/render.h @@ -0,0 +1,55 @@ +// render.h +// functions for "rendering" data onto the terminal + +#include "parser/fight_results/fight_results.h" +#include "utils/memory.h" + +// ANSI escape sequence colors +#define COLOR_GOLD 33 +#define COLOR_GRAY 90 +#define COLOR_RED 91 +#define COLOR_GREEN 92 +#define COLOR_YELLOW 93 +#define COLOR_BLUE 94 +#define COLOR_PURPLE 95 +#define COLOR_AQUA 96 +#define COLOR_WHITE 97 + +#define TABLE_ROW_SEPARATOR_CHAR '-' + +#define TABLE_LINE_START "| " +#define TABLE_LINE_END " |" +#define TABLE_LINE_SEPARATOR " | " +#define TABLE_LINE_START_WIDTH (sizeof(TABLE_LINE_START) - 1) +#define TABLE_LINE_END_WIDTH (sizeof(TABLE_LINE_END) - 1) +#define TABLE_LINE_SEPARATOR_WIDTH (sizeof(TABLE_LINE_SEPARATOR) - 1) + +// column info +typedef struct { + char *name; + size_t width; + int color; +} ColumnInfo; +static const ColumnInfo columns[] = { + {"Nick (LVL)", 17, COLOR_WHITE}, + {"EXP", 7, COLOR_WHITE}, + {"Gold", 7, COLOR_YELLOW}, + {"Psycho", 6, COLOR_AQUA}, + {"Saturation", 13, COLOR_GREEN}, + {"Splinters", 9, COLOR_BLUE}, +}; +static const inline size_t sum_col_widths() { + size_t sum = 0; + for (size_t i = 0; i < ARR_LEN(columns); i++) + sum += columns[i].width; + return sum; +} + +#define TABLE_LINE_WIDTH (TABLE_LINE_START_WIDTH + sum_col_widths() + (ARR_LEN(columns) - 1) * TABLE_LINE_SEPARATOR_WIDTH + TABLE_LINE_END_WIDTH) + +#define TABLE_ITEMS_WIDTH (TABLE_LINE_WIDTH - TABLE_LINE_START_WIDTH - TABLE_LINE_END_WIDTH) + +// render fight results onto the terminal +// params: +// - results - fight results to render +extern void render_fight_results(FightResults results); diff --git a/analyzer/structures/hashmap.c b/analyzer/structures/hashmap.c new file mode 100644 index 0000000..5eb27ae --- /dev/null +++ b/analyzer/structures/hashmap.c @@ -0,0 +1,116 @@ +#include "structures/hashmap.h" + +#include <stdlib.h> +#include <string.h> + +#include "utils/memory.h" + +HashMap *hashmap_new( + size_t key_size, + size_t value_size, + size_t (*hash)(void *key), + int (*compare)(void *key1, void *key2), + void (*key_cleanup)(void *key), + void (*value_cleanup)(void *key) +) { + HashMap *map = safe_malloc(sizeof(HashMap)); + // initialize NULLs + memset(map->table, 0, HASHMAP_TABLE_SIZE * sizeof(HashMapNode *)); + map->key_size = key_size; + map->value_size = value_size; + map->hash = hash; + map->compare = compare; + map->key_cleanup = key_cleanup; + map->value_cleanup = value_cleanup; + return map; +} + +void hashmap_put(HashMap *map, void *key, void *value) { + size_t key_hash = map->hash(key); + HashMapNode *node = map->table[key_hash]; + + // check if it exists + while (node) { + // if it exists - update node + if (map->compare(key, node->key)) { + // ensure no hanging memory allocation + if (map->value_cleanup) { + map->value_cleanup(node->value); + } + memcpy(node->value, value, map->value_size); + return; + } + node = node->next; + } + + // if it doesn't exist - create new node + HashMapNode *new_node = safe_malloc(sizeof(HashMapNode)); + new_node->key = safe_malloc(map->key_size); + new_node->value = safe_malloc(map->value_size); + memcpy(new_node->key, key, map->key_size); + memcpy(new_node->value, value, map->value_size); + + // put in front of the linked list + new_node->next = map->table[key_hash]; + map->table[key_hash] = new_node; +} + +void *hashmap_get(HashMap *map, void *key) { + size_t key_hash = map->hash(key); + HashMapNode *node = map->table[key_hash]; + + // check if it exists + while (node) { + // if it exists - return the value pointer + if (map->compare(key, node->key)) { + return node->value; + } + node = node->next; + } + + // if it doesn't exist - return NULL + return NULL; +} + +void free_node_without_next(HashMap *map, HashMapNode *node) { + if (map->key_cleanup) { + map->key_cleanup(node->key); + } + if (map->value_cleanup) { + map->value_cleanup(node->value); + } + free(node->key); + free(node->value); + free(node); +} + +void hashmap_delete(HashMap *map, void *key) { + size_t key_hash = map->hash(key); + HashMapNode **p_node = &map->table[key_hash]; + + // check if it exists + while (*p_node) { + // if it exists - delete it + if (map->compare(key, (*p_node)->key)) { + HashMapNode *next = (*p_node)->next; + free_node_without_next(map, *p_node); + *p_node = next; + return; + } + p_node = &(*p_node)->next; + } +} + +void hashmap_free(HashMap *map) { + if (!map) + return; + for (size_t i = 0; i < HASHMAP_TABLE_SIZE; i++) { + HashMapNode *curr = map->table[i]; + while (curr) { + HashMapNode *next = curr->next; + free_node_without_next(map, curr); + curr = next; + } + } + free(map); +} diff --git a/analyzer/structures/hashmap.h b/analyzer/structures/hashmap.h new file mode 100644 index 0000000..528f24a --- /dev/null +++ b/analyzer/structures/hashmap.h @@ -0,0 +1,81 @@ +// structures/hashmap.h +// a simple implementation of hashmap using linked list + +#include <stddef.h> + +#define HASHMAP_TABLE_SIZE 128 + +typedef struct HashMapNode { + void *key; + void *value; + struct HashMapNode *next; +} HashMapNode; + +typedef struct { + HashMapNode *table[HASHMAP_TABLE_SIZE]; + size_t key_size; + size_t value_size; + size_t (*hash)(void *key); + int (*compare)(void *key1, void *key2); + void (*key_cleanup)(void *key); + void (*value_cleanup)(void *value); +} HashMap; + +// create a new hash map +// params: +// - key_size - size of the key type +// - value_size - size of the value type +// - hash - function for hashing keys +// params: +// - key - pointer to the key to hash +// returns: +// hash of the key (size_t in [0; HASHMAP_TABLE_SIZE)) +// - compare - function for comparing keys +// params: +// - key1 - pointer to the first of the keys to compare +// - key2 - pointer to the second of the keys to compare +// returns: +// truthy if the keys are identical, falsy if they are different +// - key_cleanup - function for cleaning up any internal allocations inside node->key (not the key itself) +// if it has no heap allocations, this should be NULL +// params: +// - key - pointer to the key to cleanup +// - value_cleanup - function for cleaning up any internal allocations inside node->value (not the value itself) +// if it has no heap allocations, this should be NULL +// params: +// - value - pointer to the value to cleanup +// returns: +// pointer to the new hash map +extern HashMap *hashmap_new( + size_t key_size, + size_t value_size, + size_t (*hash)(void *key), + int (*compare)(void *key1, void *key2), + void (*key_cleanup)(void *key), + void (*value_cleanup)(void *value) +); + +// create/update an entry in a hash map +// params: +// - map - hash map to insert to +// - key - pointer to the key +// - value - pointer to the value +extern void hashmap_put(HashMap *map, void *key, void *value); + +// get the value of given key from a hash map +// params: +// - map - hash map to get from +// - key - pointer to the key of the value to get +// returns: +// pointer to the value of specified key or NULL if it doesn't exist +extern void *hashmap_get(HashMap *map, void *key); + +// delete an entry from a hash map +// params: +// - map - hash map to delete from +// - key - pointer to the key of the entry to delete +extern void hashmap_delete(HashMap *map, void *key); + +// free the hash map +// - map - hash map to free +extern void hashmap_free(HashMap *map); diff --git a/analyzer/structures/heap.c b/analyzer/structures/heap.c new file mode 100644 index 0000000..1267668 --- /dev/null +++ b/analyzer/structures/heap.c @@ -0,0 +1,113 @@ +#include "structures/heap.h" + +#include <stdlib.h> +#include <string.h> + +#include "utils/memory.h" + +Heap *heap_new(size_t element_size, int (*compare)(void *element1, void *element2), void (*element_cleanup)(void *element)) { + Heap *heap = safe_malloc(sizeof(Heap)); + heap->data = safe_malloc(INIT_HEAP_CAPACITY * sizeof(void *)); + heap->n_elements = 0; + heap->element_size = element_size; + heap->capacity = INIT_HEAP_CAPACITY; + heap->compare = compare; + heap->element_cleanup = element_cleanup; + return heap; +} + +// return index of parent in heap +size_t parent(size_t i) { + return (i - 1) / 2; +} + +// return index of left child in heap +size_t left(size_t i) { + return 2 * i + 1; +} + +// return index of right child in heap +size_t right(size_t i) { + return 2 * i + 2; +} + +// swap two elements +void swap(void **p_el1, void **p_el2) { + void *tmp = *p_el1; + *p_el1 = *p_el2; + *p_el2 = tmp; +} + +// expand heap exponentially to fit el_count elements +void expand_heap(Heap *heap, size_t el_count) { + while (heap->capacity < el_count) + heap->capacity = (heap->capacity << 1) | 1; + heap->data = safe_realloc(heap->data, heap->capacity * sizeof(void *)); +} + +void heap_insert(Heap *heap, void *element) { + // expand heap if capacity is too small + if (heap->n_elements >= heap->capacity) + expand_heap(heap, heap->n_elements + 1); + + // insert into the last position + size_t i = heap->n_elements; + heap->data[i] = safe_malloc(heap->element_size); + memcpy(heap->data[i], element, heap->element_size); + heap->n_elements++; + + // heapify up + while (i > 0 && heap->compare(heap->data[i], heap->data[parent(i)])) { + swap(&heap->data[i], &heap->data[parent(i)]); + i = parent(i); + } +} + +void *heap_top_get(Heap *heap) { + return heap->n_elements <= 0 ? NULL : heap->data[0]; +} + +void free_heap_element(Heap *heap, size_t index) { + if (heap->element_cleanup) + heap->element_cleanup(heap->data[index]); + free(heap->data[index]); +} + +void heap_top_remove(Heap *heap) { + if (heap->n_elements <= 0) + return; + + free_heap_element(heap, 0); + + // move last element to root + heap->data[0] = heap->data[--heap->n_elements]; + + // heapify down + size_t i = 0; + int changed; + do { + size_t l = left(i); + size_t r = right(i); + size_t to_be_parent = i; + + if (l < heap->n_elements && heap->compare(heap->data[l], heap->data[to_be_parent])) + to_be_parent = l; + if (r < heap->n_elements && heap->compare(heap->data[r], heap->data[to_be_parent])) + to_be_parent = r; + + if ((changed = to_be_parent != i)) { + swap(&heap->data[to_be_parent], &heap->data[i]); + i = to_be_parent; + } + } while (changed); +} + +void heap_free(Heap *heap) { + if (!heap) + return; + for (size_t i = 0; i < heap->n_elements; i++) { + free_heap_element(heap, i); + } + free(heap->data); + free(heap); +} diff --git a/analyzer/structures/heap.h b/analyzer/structures/heap.h new file mode 100644 index 0000000..1ae72c0 --- /dev/null +++ b/analyzer/structures/heap.h @@ -0,0 +1,60 @@ +// structures/heap.h +// a simple implementation of heap + +#include <stddef.h> + +#ifndef HEAP_H +#define HEAP_H + +#define INIT_HEAP_DEPTH 7 +#define INIT_HEAP_CAPACITY (1 << INIT_HEAP_DEPTH) - 1 + +typedef struct { + void **data; + size_t n_elements; + size_t element_size; + size_t capacity; + int (*compare)(void *element1, void *element2); + void (*element_cleanup)(void *element); +} Heap; + +// create a new heap +// params: +// - element_size - size of the heap element +// - compare - function for comparing elements +// params: +// - element1 - pointer to the first of the elements to compare +// - element2 - pointer to the second of the elements to compare +// returns: +// truthy if element1 should be element2's parent (e.g. element1 > element2 for max heap), falsy otherwise +// - element_cleanup - function for cleaning up any internal allocations inside element (not the element itself) +// if it has no heap allocations, this should be NULL +// params: +// - element - pointer to the element to cleanup +// returns: +// pointer to the new heap +extern Heap *heap_new(size_t element_size, int (*compare)(void *element1, void *element2), void (*element_cleanup)(void *element)); + +// insert new element into the heap +// params: +// - heap - heap to insert to +// - element - pointer to the element to insert +extern void heap_insert(Heap *heap, void *element); + +// get the top element from the heap +// params: +// - heap - heap to get the top element from +// returns: +// pointer to the top element in the heap or NULL if heap is empty +extern void *heap_top_get(Heap *heap); + +// remove the top element from the heap +// params: +// - heap - heap to remove the top element from +extern void heap_top_remove(Heap *heap); + +// free the heap +// - heap - heap to free +extern void heap_free(Heap *heap); + +#endif diff --git a/analyzer/utils/fs.c b/analyzer/utils/fs.c index 76fb581..5e3eb1c 100644 --- a/analyzer/utils/fs.c +++ b/analyzer/utils/fs.c @@ -1,8 +1,9 @@ -#include "utils.h" +#include "utils/fs.h" -#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <sys/stat.h> -#include <unistd.h> void get_home_dir(char *home_dir) { strcpy(home_dir, getenv("HOME")); @@ -35,9 +36,3 @@ void create_necessary_directories() { mkdir(capture_dir, 0755); mkdir(saves_dir, 0755); } - -void write_file(char *path, void *contents, int contents_len, int flags, mode_t permissions) { - int fd = open(path, flags, permissions); - write(fd, contents, contents_len); - close(fd); -} diff --git a/analyzer/utils/fs.h b/analyzer/utils/fs.h index 6335799..8083ddc 100644 --- a/analyzer/utils/fs.h +++ b/analyzer/utils/fs.h @@ -1,8 +1,6 @@ // utils/fs.h // functions for operating on the file system, i.e. creating directories, writing files, etc. -#include <sys/types.h> - // get main directory used by the program // params: // - main_dir - buffer to save the directory to @@ -20,12 +18,3 @@ extern void get_saves_dir(char *saves_dir); // create directories that will be used by the program extern void create_necessary_directories(); - -// writes file to the specified path -// params: -// - path - path to write the file to -// - contents - contents of the file -// - contents_len - amount of bytes of contents to write -// - flags - flags to open the file with -// - permissions - permissions of the written file -extern void write_file(char *path, void *contents, int contents_len, int flags, mode_t permissions); diff --git a/analyzer/utils/memory.c b/analyzer/utils/memory.c new file mode 100644 index 0000000..91c5f18 --- /dev/null +++ b/analyzer/utils/memory.c @@ -0,0 +1,62 @@ +#include "memory.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +void *safe_malloc(size_t size) { + for (size_t i = 0; i < MAX_ALLOC_ATTEMPTS; i++) { + void *p = malloc(size); + if (p) + return p; + if (i < MAX_ALLOC_ATTEMPTS - 1) { + fprintf(stderr, "Failed malloc. Retrying in 1s...\n"); + sleep(1); + } + } + + fprintf(stderr, "FATAL: Failed malloc %d times. Exiting...", MAX_ALLOC_ATTEMPTS); + exit(EXIT_FAILURE); +} + +void *safe_realloc(void *ptr, size_t size) { + if (!ptr) + return safe_malloc(size); + + for (size_t i = 0; i < MAX_ALLOC_ATTEMPTS; i++) { + void *p = realloc(ptr, size); + if (p) + return p; + if (i < MAX_ALLOC_ATTEMPTS - 1) { + fprintf(stderr, "Failed realloc. Retrying in 1s...\n"); + sleep(1); + } + } + + fprintf(stderr, "FATAL: Failed realloc %d times. Exiting...", MAX_ALLOC_ATTEMPTS); + exit(EXIT_FAILURE); +} + +// expand an array +// returns pointer to the expanded array +void *array_expand(void *arr, size_t el_size, size_t *cap, size_t to_fit) { + while (*cap < to_fit) + *cap *= 2; + return safe_realloc(arr, *cap * el_size); +} + +void *array_append(void *arr, size_t el_size, size_t *len, size_t *cap, void *val) { + if (*len >= *cap) + arr = array_expand(arr, el_size, cap, *len + 1); + memcpy((char *)arr + (*len)++ * el_size, val, el_size); + return arr; +} + +void *array_concat(void *arr1, size_t el_size, size_t *len1, size_t *cap, void *arr2, size_t len2) { + if (*len1 + len2 > *cap) + arr1 = array_expand(arr1, el_size, cap, *len1 + len2); + memcpy((char *)arr1 + *len1 * el_size, arr2, len2 * el_size); + *len1 += len2; + return arr1; +} diff --git a/analyzer/utils/memory.h b/analyzer/utils/memory.h new file mode 100644 index 0000000..0973623 --- /dev/null +++ b/analyzer/utils/memory.h @@ -0,0 +1,46 @@ +// utils/memory.h +// helper functions for dealing with memory management + +#include <stddef.h> + +#define ARR_LEN(arr) (sizeof(arr) / sizeof(arr[0])) + +#define MAX_ALLOC_ATTEMPTS 3 + +// tries malloc MAX_ALLOC_ATTEMPTS times, if all fail - exits the program +// params: +// - size - count of bytes to allocate +// returns: +// pointer to the allocated memory +extern void *safe_malloc(size_t size); + +// tries realloc MAX_ALLOC_ATTEMPTS times, if all fail - exits the program +// params: +// - ptr - pointer to the memory to reallocate +// - size - new size of the memory +// returns: +// pointer to the reallocated memory +extern void *safe_realloc(void *ptr, size_t size); + +// append to array, expand if necessary +// params: +// - arr - array to append to +// - el_size - size of an array element +// - len - pointer to the length of the array +// - cap - pointer to the capacity of the array +// - val - pointer to the value to append to the array +// returns: +// new pointer to the array (different if it was realloced) +extern void *array_append(void *arr, size_t el_size, size_t *len, size_t *cap, void *val); + +// concatenate 2 arrays, expand if necessary +// params: +// - arr1 - first array (destination) +// - el_size - size of an array element +// - len1 - pointer to the length of arr1 +// - cap - pointer to the capacity of arr1 +// - arr2 - second array +// - len2 - length of arr2 +// returns: +// new pointer to the array (different if it was realloced) +extern void *array_concat(void *arr1, size_t el_size, size_t *len1, size_t *cap, void *arr2, size_t len2); diff --git a/analyzer/utils/roman.c b/analyzer/utils/roman.c new file mode 100644 index 0000000..4fbe5c0 --- /dev/null +++ b/analyzer/utils/roman.c @@ -0,0 +1,15 @@ +#include "utils/roman.h" + +#include <string.h> + +#include "utils/memory.h" + +void to_roman(char *buffer, int number) { + buffer[0] = '\0'; + for (size_t i = 0; i < ARR_LEN(roman_symbols); i++) { + for (size_t j = 0; j < number / roman_symbols[i].num; j++) { + strcat(buffer, roman_symbols[i].symbol); + } + number %= roman_symbols[i].num; + } +} diff --git a/analyzer/utils/roman.h b/analyzer/utils/roman.h new file mode 100644 index 0000000..cebd16e --- /dev/null +++ b/analyzer/utils/roman.h @@ -0,0 +1,29 @@ +// utils/roman.h +// functions for roman numbers + +typedef struct { + int num; + char *symbol; +} RomanSymbol; + +static const RomanSymbol roman_symbols[] = { + {1000, "M"}, + {900, "CM"}, + {500, "D"}, + {400, "CD"}, + {100, "C"}, + {90, "XC"}, + {50, "L"}, + {40, "XL"}, + {10, "X"}, + {9, "IX"}, + {5, "V"}, + {4, "IV"}, + {1, "I"}, +}; + +// convert a arabic number to roman number +// params: +// buffer - buffer to save the roman number to +// number - arabic number to convert +extern void to_roman(char *buffer, int number); diff --git a/analyzer/utils/tcp.c b/analyzer/utils/tcp.c new file mode 100644 index 0000000..6309e64 --- /dev/null +++ b/analyzer/utils/tcp.c @@ -0,0 +1,177 @@ +#include "utils/tcp.h" + +#include <netinet/if_ether.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <stdlib.h> +#include <string.h> + +#include "utils/memory.h" +#include "utils/tcp_seq_math.h" + +// initalize default TCP segment +TCPSegment new_segment() { + // clang-format off + TCPSegment segment = { + .payload = NULL, + .len = 0, + .seq = 0, + .port = 0 + }; + // clang-format on + return segment; +} + +// segments compare function to use with heap +int segments_compare(void *segment1, void *segment2) { + TCPSegment *seg1 = segment1; + TCPSegment *seg2 = segment2; + return seq_before(seg1->seq, seg2->seq); +} + +// segment cleanup function to use with heap +void segment_cleanup(void *segment) { + TCPSegment *seg = segment; + free(seg->payload); +} + +TCPSegment get_segment_from_packet(const byte_t *packet, const struct pcap_pkthdr *packet_data) { + TCPSegment segment = new_segment(); + + // get Ethernet header + const struct ether_header *eth_header = (const struct ether_header *)packet; + int eth_header_len = sizeof(struct ether_header); + + // check if it's IP + if (ntohs(eth_header->ether_type) != ETHERTYPE_IP) + return segment; + + // get IP header + const struct ip *ip_header = (const struct ip *)(packet + eth_header_len); + int ip_header_len = ip_header->ip_hl * 4; + + // check if it's TCP + if (ip_header->ip_p != IPPROTO_TCP) + return segment; + + // get TCP header + const struct tcphdr *tcp_header = (const struct tcphdr *)(packet + eth_header_len + ip_header_len); + int tcp_header_len = tcp_header->th_off * 4; + + // get TCP sequence number and destination port + segment.seq = ntohl(tcp_header->seq); + segment.port = ntohs(tcp_header->th_dport); + + // get payload + int total_header_len = eth_header_len + ip_header_len + tcp_header_len; + segment.len = packet_data->caplen - total_header_len; + segment.payload = safe_malloc(segment.len); + memcpy(segment.payload, packet + total_header_len, segment.len); + return segment; +} + +TCPStream new_stream() { + // clang-format off + TCPStream stream = { + .data = safe_malloc(INIT_STREAM_CAPACITY), + .len = 0, + .capacity = INIT_STREAM_CAPACITY, + .seq = 0, + .port = 0, + .pending = NULL + }; + // clang-format on + return stream; +} + +void destroy_stream(TCPStream stream) { + free(stream.data); + heap_free(stream.pending); +} + +void remove_parsed_data(TCPStream *stream, size_t parsed_len) { + memmove(stream->data, stream->data + parsed_len, stream->len - parsed_len); + stream->len -= parsed_len; + stream->seq += parsed_len; +} + +// get the index of the first relevant byte in segment (its tcp sequence number is the one stream is expecting) +// return the index of the first relevant byte in payload or -1 if segment isn't directly connected to stream +int get_first_relevant_byte_index(TCPStream stream, TCPSegment segment) { + // expected tcp sequence numbers for stream and segment + uint32_t stream_expected = stream.seq + stream.len; + uint32_t segment_expected = segment.seq + segment.len; + if (seq_in_range_ie(stream_expected, segment.seq, segment_expected)) + return stream_expected - segment.seq; + return -1; +} + +// check if the segment is in acceptable range to the stream +int in_acceptable_range(TCPStream stream, TCPSegment segment) { + // expected tcp sequence number for stream and max acceptable sequence number for segment + uint32_t stream_expected = stream.seq + stream.len; + uint32_t max_acceptable_seq = stream_expected + MAX_SEQ_OFFSET; + return seq_in_range_ei(segment.seq, stream_expected, max_acceptable_seq); +} + +// add segment directly to stream +void add_segment(TCPStream *stream, TCPSegment segment, size_t data_start) { + stream->data = array_concat(stream->data, 1, &stream->len, &stream->capacity, segment.payload + data_start, segment.len - data_start); +} + +// add pending segments to the stream +void add_pending_segments(TCPStream *stream) { + int top_changed = 1; + TCPSegment *top; + while (top_changed && (top = heap_top_get(stream->pending))) { + top_changed = 0; + + // add pending segment it it became directly connected + int data_start = get_first_relevant_byte_index(*stream, *top); + if (data_start != -1) { + add_segment(stream, *top, data_start); + top_changed = 1; + } + + // remove top if it was just added or no longer is in acceptable range (e.g. was contained within another one pending) + if (top_changed || !in_acceptable_range(*stream, *top)) { + heap_top_remove(stream->pending); + } + } +} + +int handle_segment(TCPStream *stream, TCPSegment segment) { + // add the segment if port is different (new connection) + if (stream->port != segment.port) { + // create new heap for new pending segments + heap_free(stream->pending); + stream->pending = heap_new(sizeof(TCPSegment), segments_compare, segment_cleanup); + + stream->len = 0; + stream->seq = segment.seq; + stream->port = segment.port; + + add_segment(stream, segment, 0); + free(segment.payload); + return 1; + } + + // add segment directly to the stream if it's directly connected + int data_start = get_first_relevant_byte_index(*stream, segment); + if (data_start != -1) { + add_segment(stream, segment, data_start); + free(segment.payload); + add_pending_segments(stream); + return 1; + } + + // add to pending if segment is in acceptable range: + if (in_acceptable_range(*stream, segment)) { + heap_insert(stream->pending, &segment); + return 0; + } + + // if neither, discard entirely + free(segment.payload); + return 0; +} diff --git a/analyzer/utils/tcp.h b/analyzer/utils/tcp.h new file mode 100644 index 0000000..13cbb15 --- /dev/null +++ b/analyzer/utils/tcp.h @@ -0,0 +1,69 @@ +// utils/tcp.h +// functions for handling TCP streams/segments + +#include <pcap.h> + +#include "structures/heap.h" + +#ifndef TCP_H +#define TCP_H + +typedef unsigned char byte_t; + +// struct to persistently store TCP segments +// payload field is malloced and needs to be freed +typedef struct { + byte_t *payload; // payload in the segment + size_t len; // length of the payload + uint32_t seq; // TCP sequence number of the segment + uint16_t port; // destination (client) port of connection the segment was received from +} TCPSegment; + +// get TCP segment from captured packet +// params: +// - packet - captured packet +// - packet_data - metadata about packet +// returns: +// TCPSegment from packet, payload is NULL if the received packet was incorrect +extern TCPSegment get_segment_from_packet(const byte_t *packet, const struct pcap_pkthdr *packet_data); + +// struct that stores all the information about stream +typedef struct { + byte_t *data; // bytes in the stream + size_t len; // length of the stream + size_t capacity; // capacity of the data buffer + uint32_t seq; // TCP sequence number of the first byte in the stream + uint16_t port; // destination (client) port of current analyzed connection + Heap *pending; // min heap of segments yet to be added to stream (sorted by TCP sequence number) +} TCPStream; + +#define INIT_STREAM_CAPACITY 256 + +// initialize new stream +// returns: +// newly created TCPStream structure with initial values +extern TCPStream new_stream(); + +// cleanup memory allocations within stream +// params: +// stream - stream to destroy +extern void destroy_stream(TCPStream stream); + +// remove parsed data from the stream +// params: +// - stream - pointer to the stream to remove a part of +// - parsed_len - count of n first bytes of the stream that were parsed +extern void remove_parsed_data(TCPStream *stream, size_t parsed_len); + +// max difference between expected TCP seq and received TCP seq to still accept it into pending +#define MAX_SEQ_OFFSET 100000 + +// add TCP segment to stream, pending or completely discard it +// params: +// - stream - pointer to the current stream +// - segment - segment to handle +// returns: +// 1 if the segment was added to the stream, 0 if it was discarded or saved to pending +extern int handle_segment(TCPStream *stream, TCPSegment segment); + +#endif diff --git a/analyzer/utils/tcp_seq_math.c b/analyzer/utils/tcp_seq_math.c new file mode 100644 index 0000000..2cc6e3f --- /dev/null +++ b/analyzer/utils/tcp_seq_math.c @@ -0,0 +1,41 @@ +#include "utils/tcp_seq_math.h" + +int seq_after(uint32_t a, uint32_t b) { + return (int32_t)(a - b) > 0; +} + +int seq_before(uint32_t a, uint32_t b) { + return (int32_t)(a - b) < 0; +} + +int seq_after_eq(uint32_t a, uint32_t b) { + return (int32_t)(a - b) >= 0; +} + +int seq_before_eq(uint32_t a, uint32_t b) { + return (int32_t)(a - b) <= 0; +} + +int seq_in_range_ii(uint32_t value, uint32_t range_start, uint32_t range_end) { + int after_eq = seq_after_eq(value, range_start); + int before_eq = seq_before_eq(value, range_end); + return after_eq && before_eq; +} + +int seq_in_range_ee(uint32_t value, uint32_t range_start, uint32_t range_end) { + int after = seq_after(value, range_start); + int before = seq_before(value, range_end); + return after && before; +} + +int seq_in_range_ie(uint32_t value, uint32_t range_start, uint32_t range_end) { + int after_eq = seq_after_eq(value, range_start); + int before = seq_before(value, range_end); + return after_eq && before; +} + +int seq_in_range_ei(uint32_t value, uint32_t range_start, uint32_t range_end) { + int after = seq_after(value, range_start); + int before_eq = seq_before_eq(value, range_end); + return after && before_eq; +} diff --git a/analyzer/utils/tcp_seq_math.h b/analyzer/utils/tcp_seq_math.h new file mode 100644 index 0000000..c4da5f9 --- /dev/null +++ b/analyzer/utils/tcp_seq_math.h @@ -0,0 +1,28 @@ +// utils/tcp_seq_math.h +// helper functions for math regarding tcp sequence numbers which correctly handle overflow + +#include <stdint.h> + +// check if a is after b +extern int seq_after(uint32_t a, uint32_t b); + +// check if a is before b +extern int seq_before(uint32_t a, uint32_t b); + +// check if a is after or equal to b +extern int seq_after_eq(uint32_t a, uint32_t b); + +// check if a is before or equal to b +extern int seq_before_eq(uint32_t a, uint32_t b); + +// check if value is in [range_start; range_end] +extern int seq_in_range_ii(uint32_t value, uint32_t range_start, uint32_t range_end); + +// check if value is in (range_start; range_end) +extern int seq_in_range_ee(uint32_t value, uint32_t range_start, uint32_t range_end); + +// check if value is in [range_start; range_end) +extern int seq_in_range_ie(uint32_t value, uint32_t range_start, uint32_t range_end); + +// check if value is in (range_start; range_end] +extern int seq_in_range_ei(uint32_t value, uint32_t range_start, uint32_t range_end); diff --git a/analyzer/utils/time.c b/analyzer/utils/time.c index 72c0bc7..d95da65 100644 --- a/analyzer/utils/time.c +++ b/analyzer/utils/time.c @@ -1,4 +1,4 @@ -#include "time.h" +#include "utils/time.h" #include <stdio.h> #include <time.h> diff --git a/csv/artifact_sizes.csv b/csv/artifact_sizes.csv new file mode 100644 index 0000000..a9af916 --- /dev/null +++ b/csv/artifact_sizes.csv @@ -0,0 +1,5 @@ +id,artifact_size +1,Sub +2,Bi +3,Magni +4,Arcy diff --git a/csv/items.csv b/csv/items.csv new file mode 100644 index 0000000..0265384 --- /dev/null +++ b/csv/items.csv @@ -0,0 +1,6 @@ +id,item_name +1222,Daimonit +1219,Trop trwogi +986,KrysztaĆ czasu +703,Glif arcymistrzowski +697,Glif bakalarski diff --git a/csv/orbs.csv b/csv/orbs.csv new file mode 100644 index 0000000..02dfde4 --- /dev/null +++ b/csv/orbs.csv @@ -0,0 +1,3 @@ +id,orb_name +21,Ha'at +12,Se'at diff --git a/csv/saturations.csv b/csv/saturations.csv new file mode 100644 index 0000000..35797cb --- /dev/null +++ b/csv/saturations.csv @@ -0,0 +1,8 @@ +id,saturation_type +1,Gherudu +2,Velhar +3,Yantrei +4,Hegun +5,Solnai +6,Farao +7,Kosani diff --git a/csv_table_gen.c b/csv_table_gen.c new file mode 100644 index 0000000..ae80b78 --- /dev/null +++ b/csv_table_gen.c @@ -0,0 +1,125 @@ +#include <dirent.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> + +#define EXT ".csv" +#define EXT_LEN strlen(".csv") +#define CSV_PATH "csv" +#define OUT_PATH "analyzer/" CSV_PATH + +#define BUFFER_SIZE 256 +#define MAX_ARR_SIZE 4096 + +// read values from input csv and save them to buffers +// returns max_index of the array +int read_csv(char *path, char *from, char *to, char from_to_array[][BUFFER_SIZE]) { + FILE *fp = fopen(path, "r"); + + // read field names + fscanf(fp, "%[^,],%[^\n]", from, to); + + // read values + size_t max_index = 0; + size_t val1; + char val2[BUFFER_SIZE]; + while (fscanf(fp, "%zu,%[^\n]", &val1, val2) == 2) { + strcpy(from_to_array[val1], val2); + if (val1 > max_index) + max_index = val1; + } + + fclose(fp); + + return max_index; +} + +// create output .h file +void create_h(char *path, char *from, char *to) { + FILE *fp = fopen(path, "w"); + + // write comments and function prototype + fprintf(fp, "// %s\n", strstr(path, CSV_PATH)); + fprintf(fp, "// translate %s to %s - generated from csv\n\n", from, to); + fprintf(fp, "// get %s from %s\n", to, from); + fprintf(fp, "// params:\n"); + fprintf(fp, "// - %s - %s to translate from\n", from, from); + fprintf(fp, "// returns:\n"); + fprintf(fp, "// %s corresponding to %s\n", to, from); + fprintf(fp, "extern const char *get_%s_from_%s(int %s);", to, from, from); + + fclose(fp); +} + +// create output .c file +void create_c(char *path, char *h_path, char *from, char *to, char from_to_array[][BUFFER_SIZE], size_t max_index) { + FILE *fp = fopen(path, "w"); + + char arr_name[BUFFER_SIZE], arr_len_name[BUFFER_SIZE]; + sprintf(arr_name, "%s_to_%s", from, to); + sprintf(arr_len_name, "%s_to_%s_len", from, to); + + // write includes and the beginning of array + fprintf(fp, "#include \"%s\"\n\n", strstr(h_path, CSV_PATH)); + fprintf(fp, "#include <stdio.h>\n\n"); + fprintf(fp, "static const char *%s[] = {\n", arr_name); + + // write values + for (size_t i = 0; i <= max_index; i++) { + if (strlen(from_to_array[i]) > 0) + fprintf(fp, " \"%s\",\n", from_to_array[i]); + else + fprintf(fp, " \"%zu\",\n", i); + } + + // write the end of array and len + fprintf(fp, "};\n"); + fprintf(fp, "int %s = %zu;\n\n", arr_len_name, max_index + 1); + + // write the getter + fprintf(fp, "const char *get_%s_from_%s(int %s) {\n", to, from, from); + fprintf(fp, " if (%s >= 0 && %s < %s)\n", from, from, arr_len_name); + fprintf(fp, " return %s[%s];\n", arr_name, from); + fprintf(fp, " fprintf(stderr, \"%s = %%d is outside of table range [0; %%d]\\n\", %s, %s - 1);\n", from, from, arr_len_name); + fprintf(fp, " return \"null\";\n"); + fprintf(fp, "}"); + + fclose(fp); +} + +void create_table(char *file_name) { + // read from input file + char input_path[BUFFER_SIZE]; + sprintf(input_path, "%s/%s", CSV_PATH, file_name); + char from[BUFFER_SIZE], to[BUFFER_SIZE]; + char from_to_array[MAX_ARR_SIZE][BUFFER_SIZE] = {0}; + size_t max_index = read_csv(input_path, from, to, from_to_array); + + // create output .h file + char h_path[BUFFER_SIZE]; + sprintf(h_path, "%s/%s_to_%s.h", OUT_PATH, from, to); + create_h(h_path, from, to); + + // create output .c file + char c_path[BUFFER_SIZE]; + sprintf(c_path, "%s/%s_to_%s.c", OUT_PATH, from, to); + create_c(c_path, h_path, from, to, from_to_array, max_index); +} + +int main() { + // make directory for output files + mkdir(OUT_PATH, 0755); + + DIR *csv_dir = opendir(CSV_PATH); + struct dirent *entry; + while ((entry = readdir(csv_dir))) { + char *file_name = entry->d_name; + size_t file_name_len = strlen(file_name); + // create lookup tables for each *.csv file + if (file_name_len > EXT_LEN && !strcmp(file_name + file_name_len - EXT_LEN, EXT)) + create_table(entry->d_name); + } + closedir(csv_dir); + + return 0; +} @@ -1,61 +1,18 @@ -#include <stdio.h> -#include <string.h> - -#include "analyzer/capture.h" -#include "analyzer/utils/fs.h" - -// prints help -void print_help() { - printf("A bare-bones analyzer for the game Broken Ranks\n"); - printf("\nUsage:\n"); - printf(" branal - start live analysis\n"); - printf(" branal (l | live) - start live analysis (same as above)\n"); - printf(" branal (c | capture) [file] - start capturing packets to [file].\n"); - printf(" If [file] not given - captures to $HOME/.local/share/branal/captures\n"); - printf(" branal (f | file) <file> - analyze the specified <file>\n"); - printf(" branal (h | help) - display this help\n"); -} - -// starts live analyzer mode -void start_live_analyzer() { - // TODO: implement - printf("Not implemented\n"); -} - -// analyzes the specified file -// params: -// - file_path - path to the .pcap file to be analyzed -void analyze_file(char *file_path) { - // TODO: implement - printf("Not implemented\n"); -} +#include "commands.h" +#include "utils/fs.h" int main(int argc, char *argv[]) { create_necessary_directories(); - // start live analysis if no arguments provided - if (argc < 2) { - start_live_analyzer(); - return 0; - } + // start first command if no arguments provided + if (argc < 2) + return commands[0].handler(argc, argv); - if (strcmp("l", argv[1]) == 0 || strcmp("live", argv[1]) == 0) { - start_live_analyzer(); - } - else if (strcmp("c", argv[1]) == 0 || strcmp("capture", argv[1]) == 0) { - capturing_mode(argc >= 3 ? argv[2] : NULL); - } - else if (strcmp("h", argv[1]) == 0 || strcmp("help", argv[1]) == 0) { - print_help(); - } - else if (argc >= 3 && (strcmp("f", argv[1]) == 0 || strcmp("file", argv[1]) == 0)) { - analyze_file(argv[2]); - } - else { - printf("Command not recognized\n"); - printf("Use \"branal (h | help)\" for help\n"); - return 1; + for (size_t i = 0; i < commands_count; i++) { + if (is_command(commands[i], argv[1])) + return commands[i].handler(argc, argv); } - return 0; + command_not_recognized(); + return 1; } |
