├── .gitignore ├── README.md ├── bin └── linux │ ├── nwnltr │ └── nwserver-dump-decode ├── extra ├── ltr │ ├── animal.ltr │ ├── dwarff.ltr │ ├── dwarfl.ltr │ ├── dwarfm.ltr │ ├── elff.ltr │ ├── elfl.ltr │ ├── elfm.ltr │ ├── familiar.ltr │ ├── gnomef.ltr │ ├── gnomel.ltr │ ├── gnomem.ltr │ ├── halfelfl.ltr │ ├── halflingf.ltr │ ├── halflingl.ltr │ ├── halflingm.ltr │ ├── halforcf.ltr │ ├── halforcl.ltr │ ├── halforcm.ltr │ ├── humanf.ltr │ ├── humanl.ltr │ └── humanm.ltr └── offsets │ ├── FunctionsLinux-8186.hpp │ └── FunctionsWindows-8186.hpp ├── nwnltr.c ├── nwnx-server-setup ├── mod-disable.sh ├── mod-enable.sh ├── mod-savechars.sh ├── mod-start.sh ├── mod-status.sh ├── mod-stop.sh └── nwnx-setup.sh └── nwserver-dump-decode.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nwn-misc 2 | Miscellaneous standalone NWN tools 3 | 4 | ## NWNLTR 5 | 6 | A tool for displaying and generating LTR files - used for the game random name generator. 7 | 8 | - Generate random names from .ltr files like the game does 9 | - Print .ltr file Markov chain tables in a human readable format 10 | - Build a new .ltr file from a set of names 11 | 12 | ## nwserver-dump-decode 13 | 14 | A tool to decode crash logs (.log) or similar stack traces from nwserver (windows or linux). 15 | 16 | Can be fed either a nwserver-crash-xxxxxxxxx.log file, or raw offsets. Uses NWNX API Functions{Linux,Windows}.hpp to decode the offsets. 17 | 18 | ## NWNX Server setup 19 | 20 | Instructions on how to setup a NWNX server and a collection of useful scripts to run/maintain it: 21 | 22 | - mod-start.sh - starts the server unless already running 23 | - mod-stop.sh - kills the server 24 | - mod-disable.sh - disables server auto restart 25 | - mod-enable.sh - enables server auto restart 26 | - mod-savechars.sh - saves servervault/ to git 27 | - mod-status.sh - returns 1 if server is running, 0 if not 28 | -------------------------------------------------------------------------------- /bin/linux/nwnltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/bin/linux/nwnltr -------------------------------------------------------------------------------- /bin/linux/nwserver-dump-decode: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/bin/linux/nwserver-dump-decode -------------------------------------------------------------------------------- /extra/ltr/animal.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/animal.ltr -------------------------------------------------------------------------------- /extra/ltr/dwarff.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/dwarff.ltr -------------------------------------------------------------------------------- /extra/ltr/dwarfl.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/dwarfl.ltr -------------------------------------------------------------------------------- /extra/ltr/dwarfm.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/dwarfm.ltr -------------------------------------------------------------------------------- /extra/ltr/elff.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/elff.ltr -------------------------------------------------------------------------------- /extra/ltr/elfl.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/elfl.ltr -------------------------------------------------------------------------------- /extra/ltr/elfm.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/elfm.ltr -------------------------------------------------------------------------------- /extra/ltr/familiar.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/familiar.ltr -------------------------------------------------------------------------------- /extra/ltr/gnomef.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/gnomef.ltr -------------------------------------------------------------------------------- /extra/ltr/gnomel.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/gnomel.ltr -------------------------------------------------------------------------------- /extra/ltr/gnomem.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/gnomem.ltr -------------------------------------------------------------------------------- /extra/ltr/halfelfl.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/halfelfl.ltr -------------------------------------------------------------------------------- /extra/ltr/halflingf.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/halflingf.ltr -------------------------------------------------------------------------------- /extra/ltr/halflingl.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/halflingl.ltr -------------------------------------------------------------------------------- /extra/ltr/halflingm.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/halflingm.ltr -------------------------------------------------------------------------------- /extra/ltr/halforcf.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/halforcf.ltr -------------------------------------------------------------------------------- /extra/ltr/halforcl.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/halforcl.ltr -------------------------------------------------------------------------------- /extra/ltr/halforcm.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/halforcm.ltr -------------------------------------------------------------------------------- /extra/ltr/humanf.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/humanf.ltr -------------------------------------------------------------------------------- /extra/ltr/humanl.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/humanl.ltr -------------------------------------------------------------------------------- /extra/ltr/humanm.ltr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtijanic/nwn-misc/e4873d14872963466bde70d75448f907656089ba/extra/ltr/humanm.ltr -------------------------------------------------------------------------------- /nwnltr.c: -------------------------------------------------------------------------------- 1 | // Released under WTFPL-2.0 license 2 | // 3 | // LTR manipulation tool: 4 | // - Generate random names from .ltr files like the game does 5 | // - Print .ltr file Markov chain tables in a human readable format 6 | // - Build a new .ltr file from a set of names 7 | // 8 | // About LTR files: 9 | // .ltr files are used by the GetRandomName() NWN function to generate names. 10 | // They are a simple Markov chain table of cumulative distribution function (CDF) 11 | // for any sequence of up to 3 characters. 12 | // Put simpler, for any sequence of up to 3 characters, we have the probability: 13 | // - That it appears at the start of the name 14 | // - That it appears in the middle of the name 15 | // - That it appears at the end of the name 16 | // 17 | // To compile, use any of: 18 | // make nwnltr 19 | // cc -o nwnltr nwnltr.c 20 | // 21 | #include "stdio.h" 22 | #include "stdint.h" 23 | #include "stdlib.h" 24 | #include "stdarg.h" 25 | #include "string.h" 26 | #include "ctype.h" 27 | #include "time.h" 28 | 29 | #define HELP \ 30 | "NWN name generator tool\n" \ 31 | "Usage: nwnltr [OPTION] \n" \ 32 | "Options:\n" \ 33 | " -p, --print Print Markov chain tables for in a human readable format\n" \ 34 | " -b, --build Build Markov chain tables using words from stdin and store in \n" \ 35 | " -g, --generate=NUM Generate NUM names from and print to stdout. NUM=100 by default\n" \ 36 | " -s, --seed=NUM Set the RNG seed to NUM. time(NULL) by default\n" \ 37 | " -n, --nofix Do not fix corrupted tables in ltr files (if detected). default is to fix\n" 38 | 39 | struct cfg { 40 | int build; 41 | int print; 42 | int nofix; 43 | int generate; 44 | int seed; 45 | char *ltrfile; 46 | } cfg; 47 | 48 | #define die(format, ...) \ 49 | do { \ 50 | fprintf(stderr, format "\n", ##__VA_ARGS__); \ 51 | fflush(stderr); \ 52 | exit(~0); \ 53 | } while(0) 54 | 55 | void parse_cmdline(int argc, char *argv[]) { 56 | if (argc < 3) { 57 | printf(HELP); 58 | exit(0); 59 | } 60 | 61 | for (int i = 1; i < argc - 1; i++) { 62 | cfg.print |= !strcmp(argv[i], "-p") || !strcmp(argv[i], "--print"); 63 | cfg.build |= !strcmp(argv[i], "-b") || !strcmp(argv[i], "--build"); 64 | cfg.nofix |= !strcmp(argv[i], "-n") || !strcmp(argv[i], "--nofix"); 65 | 66 | sscanf(argv[i], "--seed=%d", &cfg.seed) || (!strcmp(argv[i], "-s") && sscanf(argv[i+1], "%d", &cfg.seed)); 67 | 68 | if (sscanf(argv[i], "--generate=%d", &cfg.generate) != 1) { 69 | if (!strcmp(argv[i], "--generate")) 70 | cfg.generate = 100; 71 | else if (!strcmp(argv[i], "-g")) 72 | sscanf(argv[i+1], "%d", &cfg.generate) == 1 ? i++ : (cfg.generate = 100); 73 | } 74 | } 75 | 76 | cfg.ltrfile = argv[argc-1]; 77 | if (!(cfg.print || cfg.build || cfg.generate)) { 78 | printf("Need at least one of -p, -b, -g\n" HELP); 79 | exit(0); 80 | } 81 | } 82 | 83 | // NOTE: Game does not support more than 28 letters. 84 | // Files can have fewer (just alpha), but there is no point as the special ones 85 | // can just be given a probability of 0 to achieve the same effect. 86 | // Thus, the value 28 is hardcoded here, for ease of file IO, but can be 87 | // overridden at compile time. 88 | #ifndef NUM_LETTERS 89 | #define NUM_LETTERS 28 90 | #endif 91 | static const char letters[] = "abcdefghijklmnopqrstuvwxyz'-"; 92 | struct ltr_header { 93 | char magic[8]; 94 | uint8_t num_letters; 95 | }; 96 | struct cdf { 97 | float start [NUM_LETTERS]; 98 | float middle [NUM_LETTERS]; 99 | float end [NUM_LETTERS]; 100 | }; 101 | struct ltrdata { 102 | struct cdf singles; 103 | struct cdf doubles[NUM_LETTERS]; 104 | struct cdf triples[NUM_LETTERS][NUM_LETTERS]; 105 | }; 106 | struct ltrfile { 107 | struct ltr_header header; 108 | struct ltrdata data; 109 | }; 110 | 111 | static float nrand() { return (float)rand() / RAND_MAX; } 112 | static int idx(char letter) { 113 | if (letter == '\'') return 26; 114 | if (letter == '-') return 27; 115 | if (letter >= 'a' && letter <= 'z') return letter - 'a'; 116 | return -1; 117 | } 118 | 119 | void load_ltr(const char *filename, struct ltrfile *ltr) { 120 | FILE *f = fopen(filename, "rb"); 121 | if (!f) 122 | die("Unable to open file %s", filename); 123 | 124 | if (fread(<r->header, 9, 1, f) != 1 || strncmp(ltr->header.magic, "LTR V1.0", 8)) 125 | die("File %s has no valid LTR header", filename); 126 | 127 | if (ltr->header.num_letters != NUM_LETTERS) 128 | die("File built for %d letters, tool only supports %d.", ltr->header.num_letters, NUM_LETTERS); 129 | 130 | if (fread(<r->data, sizeof(ltr->data), 1, f) != 1) 131 | die("Unable to read the prob table from %s. Truncated file?", filename); 132 | 133 | fclose(f); 134 | } 135 | 136 | void fix_ltr(struct ltrfile *ltr) { 137 | // There was a bug in the original code Bioware used to create .ltr files 138 | // which caused the single.middle and single.end tables to have their CDF 139 | // values corrupted for all entries past any which have a probability of 140 | // zero. 141 | // Fortunately, this can be corrected for in post, which we do here. 142 | 143 | // If the final nonzero value in the table is not 'exactly' 1.0, then they 144 | // are corrupt. 145 | // Note that likely due to precision loss sometime during generation by 146 | // Bioware's utility, the results, even after correction, may not exactly 147 | // accumulate to 1.000000f, so we give a small bit of leeway. 148 | int iscorrupt = 3; 149 | for (int i = 0; i < ltr->header.num_letters; i++) { 150 | if ((ltr->data.singles.middle[i] >= 0.9999) && (ltr->data.singles.middle[i] <= 1.0001)) { 151 | iscorrupt &= ~2; // the middle table is not corrupt 152 | } 153 | if ((ltr->data.singles.end[i] >= 0.9999) && (ltr->data.singles.end[i] <= 1.0001)) { 154 | iscorrupt &= ~1; // the end table is not corrupt 155 | } 156 | } 157 | if (iscorrupt & 2) { 158 | fprintf(stderr,"Correcting errors in singles.middle probability table...\n"); 159 | float accumulator = 0.0; 160 | float prevval = 0.0; 161 | float correction = 0.0; 162 | float uncorrected = 0.0; 163 | for (int i = 0; i < ltr->header.num_letters; i++) { 164 | uncorrected = ltr->data.singles.middle[i]; 165 | if (ltr->data.singles.middle[i] != 0.0) { 166 | if (i > 0) { 167 | if ((prevval == 0.0)) { 168 | correction = accumulator; 169 | } 170 | } 171 | accumulator = ltr->data.singles.middle[i]+correction; 172 | ltr->data.singles.middle[i] = accumulator; 173 | } 174 | fprintf(stderr,"ltr: %c, original: %f, corrected: %f, acc: %f, offset: %f\n", letters[i], uncorrected, ltr->data.singles.middle[i], accumulator, correction); 175 | prevval = uncorrected; 176 | } 177 | if ((accumulator < 0.9999) || (accumulator > 1.0001)) 178 | fprintf(stderr,"Warning: during fixing process, accumulator ended up at an incorrect value of %f!\n", accumulator); 179 | } 180 | if (iscorrupt & 1) { 181 | fprintf(stderr,"Correcting errors in singles.end probability table...\n"); 182 | float accumulator = 0.0; 183 | float prevval = 0.0; 184 | float correction = 0.0; 185 | float uncorrected = 0.0; 186 | for (int i = 0; i < ltr->header.num_letters; i++) { 187 | uncorrected = ltr->data.singles.end[i]; 188 | if (ltr->data.singles.end[i] != 0.0) { 189 | if (i > 0) { 190 | if ((prevval == 0.0)) { 191 | correction = accumulator; 192 | } 193 | } 194 | accumulator = ltr->data.singles.end[i]+correction; 195 | ltr->data.singles.end[i] = accumulator; 196 | } 197 | fprintf(stderr,"ltr: %c, original: %f, corrected: %f, acc: %f, offset: %f\n", letters[i], uncorrected, ltr->data.singles.end[i], accumulator, correction); 198 | prevval = uncorrected; 199 | } 200 | if ((accumulator < 0.9999) || (accumulator > 1.0001)) 201 | fprintf(stderr,"Warning: during fixing process, accumulator ended up at an incorrect value of %f!\n", accumulator); 202 | } 203 | if (iscorrupt != 0) { 204 | fprintf(stderr,"Corrections completed.\n"); 205 | fflush(stderr); 206 | } 207 | } 208 | 209 | void build_ltr(const char *filename, struct ltrfile *ltr) { 210 | memset(ltr, 0, sizeof(*ltr)); 211 | strncpy(ltr->header.magic, "LTR V1.0", 8); 212 | ltr->header.num_letters = NUM_LETTERS; 213 | 214 | char buf[256] = {0}; 215 | while (scanf("%255s", buf) == 1) { 216 | char buf2[256] = {0}; 217 | char *p = buf2, *q = buf2; 218 | for (char *r = buf; *r; r++) { 219 | if ((*r) == '#') // stop on # to allow comments 220 | break; 221 | *r = tolower(*r); 222 | if (idx(*r) == -1) { 223 | fprintf(stderr, "Invalid character %c (%02x) in name \"%s\". Skipping character.\n", *r, (uint8_t)*r, buf); 224 | fflush(stderr); 225 | continue; 226 | } 227 | *q++ = *r; 228 | } 229 | *q = '\0'; 230 | 231 | if ((q - buf2) < 3) { // we need at least 3 characters in a name 232 | fprintf(stderr, "Name \"%s\" is too short. Skipping name.\n", buf2); 233 | fflush(stderr); 234 | continue; 235 | } 236 | 237 | q--; 238 | 239 | ltr->data.singles.start[idx(p[0])] += 1.0; 240 | ltr->data.doubles[idx(p[0])].start[idx(p[1])] += 1.0; 241 | ltr->data.triples[idx(p[0])][idx(p[1])].start[idx(p[2])] += 1.0; 242 | 243 | ltr->data.singles.end[idx(q[0])] += 1.0; 244 | ltr->data.doubles[idx(q[-1])].end[idx(q[0])] += 1.0; 245 | ltr->data.triples[idx(q[-2])][idx(q[-1])].end[idx(q[0])] += 1.0; 246 | 247 | if ((q - p) == 2) continue; // No middle 248 | while (++p != q-2) { 249 | ltr->data.singles.middle[idx(p[0])] += 1.0; 250 | ltr->data.doubles[idx(p[0])].middle[idx(p[1])] += 1.0; 251 | ltr->data.triples[idx(p[0])][idx(p[1])].middle[idx(p[2])] += 1.0; 252 | } 253 | } 254 | 255 | { 256 | float s = 0.0, m = 0.0, e = 0.0; 257 | int startcount = 0, midcount = 0, endcount = 0; 258 | for (int i = 0; i < ltr->header.num_letters; i++) { 259 | startcount += (int)ltr->data.singles.start[i]; 260 | endcount += (int)ltr->data.singles.end[i]; 261 | midcount += (int)ltr->data.singles.middle[i]; 262 | } 263 | for (int i = 0; i < ltr->header.num_letters; i++) { 264 | if (ltr->data.singles.start[i] > 0.0) { 265 | ltr->data.singles.start[i] /= (float)startcount; 266 | s = ltr->data.singles.start[i] += s; 267 | } 268 | if (ltr->data.singles.end[i] > 0.0) { 269 | ltr->data.singles.end[i] /= (float)endcount; 270 | e = ltr->data.singles.end[i] += e; 271 | } 272 | if (ltr->data.singles.middle[i] > 0.0) { 273 | ltr->data.singles.middle[i] /= (float)midcount; 274 | m = ltr->data.singles.middle[i] += m; 275 | } 276 | } 277 | } 278 | for (int i = 0; i < ltr->header.num_letters; i++) { 279 | float s = 0.0, m = 0.0, e = 0.0; 280 | int startcount = 0, midcount = 0, endcount = 0; 281 | for (int j = 0; j < ltr->header.num_letters; j++) { 282 | startcount += (int)ltr->data.doubles[i].start[j]; 283 | endcount += (int)ltr->data.doubles[i].end[j]; 284 | midcount += (int)ltr->data.doubles[i].middle[j]; 285 | } 286 | for (int j = 0; j < ltr->header.num_letters; j++) { 287 | if (ltr->data.doubles[i].start[j] > 0.0) { 288 | ltr->data.doubles[i].start[j] /= (float)startcount; 289 | s = ltr->data.doubles[i].start[j] += s; 290 | } 291 | if (ltr->data.doubles[i].end[j] > 0.0) { 292 | ltr->data.doubles[i].end[j] /= (float)endcount; 293 | e = ltr->data.doubles[i].end[j] += e; 294 | } 295 | if (ltr->data.doubles[i].middle[j] > 0.0) { 296 | ltr->data.doubles[i].middle[j] /= (float)midcount; 297 | m = ltr->data.doubles[i].middle[j] += m; 298 | } 299 | } 300 | } 301 | for (int i = 0; i < ltr->header.num_letters; i++) { 302 | for (int j = 0; j < ltr->header.num_letters; j++) { 303 | float s = 0.0, m = 0.0, e = 0.0; 304 | int startcount = 0, midcount = 0, endcount = 0; 305 | for (int k = 0; k < ltr->header.num_letters; k++) { 306 | startcount += (int)ltr->data.triples[i][j].start[k]; 307 | endcount += (int)ltr->data.triples[i][j].end[k]; 308 | midcount += (int)ltr->data.triples[i][j].middle[k]; 309 | } 310 | for (int k = 0; k < ltr->header.num_letters; k++) { 311 | if (ltr->data.triples[i][j].start[k] > 0.0) { 312 | ltr->data.triples[i][j].start[k] /= (float)startcount; 313 | s = ltr->data.triples[i][j].start[k] += s; 314 | } 315 | if (ltr->data.triples[i][j].end[k] > 0.0) { 316 | ltr->data.triples[i][j].end[k] /= (float)endcount; 317 | e = ltr->data.triples[i][j].end[k] += e; 318 | } 319 | if (ltr->data.triples[i][j].middle[k] > 0.0) { 320 | ltr->data.triples[i][j].middle[k] /= (float)midcount; 321 | m = ltr->data.triples[i][j].middle[k] += m; 322 | } 323 | } 324 | } 325 | } 326 | 327 | FILE *f = fopen(filename, "wb"); 328 | if (!f) die("Unable to create file %s", filename); 329 | fwrite(<r->header, 9, 1, f); 330 | fwrite(<r->data, sizeof(ltr->data), 1, f); 331 | fclose(f); 332 | } 333 | 334 | void print_ltr(struct ltrfile *ltr) { 335 | printf("Num letters: %d\n", ltr->header.num_letters); 336 | printf("Sequence | CDF(start) P(start) | CDF(middle) P(middle) | CDF(end) P(end)\n"); 337 | 338 | float s = 0.0, m = 0.0, e = 0.0; 339 | for (int i = 0; i < ltr->header.num_letters; i++) { 340 | struct cdf *p = <r->data.singles; 341 | printf("%c |% .5f % .5f |% .5f % .5f |% .5f % .5f\n", letters[i], 342 | p->start[i], p->start[i] == 0.0 ? 0.0 : p->start[i] - s, 343 | p->middle[i], p->middle[i] == 0.0 ? 0.0 : p->middle[i] - m, 344 | p->end[i], p->end[i] == 0.0 ? 0.0 : p->end[i] - e); 345 | 346 | if (p->start[i] > 0.0) s = p->start[i]; 347 | if (p->middle[i] > 0.0) m = p->middle[i]; 348 | if (p->end[i] > 0.0) e = p->end[i]; 349 | } 350 | 351 | for (int i = 0; i < ltr->header.num_letters; i++) { 352 | s = m = e = 0.0; 353 | for (int j = 0; j < ltr->header.num_letters; j++) { 354 | struct cdf *p = <r->data.doubles[i]; 355 | printf("%c%c |% .5f % .5f |% .5f % .5f |% .5f % .5f\n", letters[i], letters[j], 356 | p->start[j], p->start[j] == 0.0 ? 0.0 : p->start[j] - s, 357 | p->middle[j], p->middle[j] == 0.0 ? 0.0 : p->middle[j] - m, 358 | p->end[j], p->end[j] == 0.0 ? 0.0 : p->end[j] - e); 359 | 360 | if (p->start[j] > 0.0) s = p->start[j]; 361 | if (p->middle[j] > 0.0) m = p->middle[j]; 362 | if (p->end[j] > 0.0) e = p->end[j]; 363 | } 364 | } 365 | 366 | for (int i = 0; i < ltr->header.num_letters; i++) { 367 | for (int j = 0; j < ltr->header.num_letters; j++) { 368 | s = m = e = 0.0; 369 | for (int k = 0; k < ltr->header.num_letters; k++) { 370 | struct cdf *p = <r->data.triples[i][j]; 371 | printf("%c%c%c |% .5f % .5f |% .5f % .5f |% .5f % .5f\n", letters[i], letters[j], letters[k], 372 | p->start[k], p->start[k] == 0.0 ? 0.0 : p->start[k] - s, 373 | p->middle[k], p->middle[k] == 0.0 ? 0.0 : p->middle[k] - m, 374 | p->end[k], p->end[k] == 0.0 ? 0.0 : p->end[k] - e); 375 | 376 | if (p->start[k] > 0.0) s = p->start[k]; 377 | if (p->middle[k] > 0.0) m = p->middle[k]; 378 | if (p->end[k] > 0.0) e = p->end[k]; 379 | } 380 | } 381 | } 382 | } 383 | 384 | const char *random_name(struct ltrfile *ltr) { 385 | static char namebuf[256]; 386 | int attempts; 387 | char *p; 388 | float prob; 389 | int i; 390 | 391 | again: 392 | attempts = 0; 393 | p = &namebuf[0]; 394 | 395 | for (i = 0, prob = nrand(); i < ltr->header.num_letters; i++) 396 | if (prob < ltr->data.singles.start[i]) 397 | break; 398 | // This can happen if the training set was too small 399 | if (i == ltr->header.num_letters) 400 | goto again; 401 | *p++ = letters[i]; 402 | 403 | for (i = 0, prob = nrand(); i < ltr->header.num_letters; i++) 404 | if (prob < ltr->data.doubles[idx(p[-1])].start[i]) 405 | break; 406 | if (i == ltr->header.num_letters) 407 | goto again; 408 | *p++ = letters[i]; 409 | 410 | for (i = 0, prob = nrand(); i < ltr->header.num_letters; i++) 411 | if (prob < ltr->data.triples[idx(p[-2])][idx(p[-1])].start[i]) 412 | break; 413 | if (i == ltr->header.num_letters) 414 | goto again; 415 | *p++ = letters[i]; 416 | 417 | while (1) { 418 | prob = nrand(); 419 | // Arbitrary end threshold form the core game 420 | if ((rand() % 12) <= (p - namebuf)) { 421 | for (i = 0; i < ltr->header.num_letters; i++) { 422 | if (prob < ltr->data.triples[idx(p[-2])][idx(p[-1])].end[i]) { 423 | *p++ = letters[i]; *p = '\0'; 424 | namebuf[0] = toupper(namebuf[0]); 425 | return namebuf; 426 | } 427 | } 428 | } 429 | 430 | for (i = 0; i < ltr->header.num_letters; i++) { 431 | if (prob < ltr->data.triples[idx(p[-2])][idx(p[-1])].middle[i]) { 432 | *p++ = letters[i]; 433 | break; 434 | } 435 | } 436 | 437 | if (i == ltr->header.num_letters) { 438 | if (--p - namebuf < 3 || ++attempts > 100) 439 | goto again; 440 | } 441 | } 442 | } 443 | 444 | int main(int argc, char *argv[]) { 445 | struct ltrfile ltr; 446 | parse_cmdline(argc, argv); 447 | 448 | srand(cfg.seed ? cfg.seed : time(NULL)); 449 | 450 | if (cfg.build) 451 | build_ltr(cfg.ltrfile, <r); 452 | else 453 | load_ltr(cfg.ltrfile, <r); 454 | 455 | if (!(cfg.nofix)) 456 | fix_ltr(<r); 457 | 458 | if (cfg.print) 459 | print_ltr(<r); 460 | 461 | while (cfg.generate-- > 0) 462 | printf("%s\n", random_name(<r)); 463 | 464 | return 0; 465 | } 466 | -------------------------------------------------------------------------------- /nwnx-server-setup/mod-disable.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | touch ~/.mod-maintenance 4 | echo "The server will not auto-start until re-enabled" 5 | -------------------------------------------------------------------------------- /nwnx-server-setup/mod-enable.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -f ~/.mod-maintenance 4 | echo "Server auto start enabled" 5 | -------------------------------------------------------------------------------- /nwnx-server-setup/mod-savechars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pushd ~/nwn/userdir/servervault 4 | git add * 5 | git commit -m "servervault backup" 6 | popd 7 | -------------------------------------------------------------------------------- /nwnx-server-setup/mod-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Set the names for your server/module here 4 | MODNAME=MyModule 5 | 6 | status=$(./mod-status.sh) 7 | if [ "$status" -eq "1" ]; then 8 | echo "$MODNAME is already running" 9 | exit; 10 | fi 11 | 12 | if [ -f /home/nwn/.mod-maintenance ]; then 13 | echo "$MODNAME maintenance in progress (.mod-maintenance file exists), not starting" 14 | exit; 15 | fi 16 | 17 | # Make a backup of all characters each time the server restarts 18 | ./mod-savechars.sh 19 | 20 | pushd ~/nwn/bin/linux-x86 21 | 22 | # Advanced plugins most people won't use, so skip them by default. 23 | export NWNX_JVM_SKIP=y 24 | export NWNX_MONO_SKIP=y 25 | export NWNX_THREADWATCHDOG_SKIP=y 26 | export NWNX_PROFILER_SKIP=y 27 | export NWNX_METRICS_INFLUXDB_SKIP=y 28 | export NWNX_RUBY_SKIP=y 29 | export NWNX_REDIS_SKIP=y 30 | export NWNX_LUA_SKIP=y 31 | export NWNX_SPELLCHECKER_SKIP=y 32 | export NWNX_TRACKING_SKIP=y 33 | 34 | # Set you DB connection info here 35 | export NWNX_SQL_TYPE=mysql 36 | export NWNX_SQL_HOST=localhost 37 | export NWNX_SQL_USERNAME=nwn 38 | export NWNX_SQL_PASSWORD=pass 39 | export NWNX_SQL_DATABASE=mymodulename 40 | export NWNX_SQL_QUERY_METRICS=true 41 | 42 | # Log levels go from 2 (only fatal errors) to 7 (very verbose). 6 is recommended 43 | export NWNX_CORE_LOG_LEVEL=6 44 | 45 | # 46 | # Custom behavior tweaks from the NWNX_Tweaks plugin. Uncomment/modify to enable 47 | # 48 | # HP players need to reach to be considered dead 49 | #export NWNX_TWEAKS_PLAYER_DYING_HP_LIMIT=-10 50 | # Disable pausing by players and DMs 51 | #export NWNX_TWEAKS_DISABLE_PAUSE=y 52 | # Disable DM quicksave ability 53 | export NWNX_TWEAKS_DISABLE_QUICKSAVE=y 54 | # Stackable items can only be merged if all local variables are the same 55 | #export NWNX_TWEAKS_COMPARE_VARIABLES_WHEN_MERGING=y 56 | # Parry functions as per description, instead of blocking max 3 attacks per round 57 | #export NWNX_TWEAKS_PARRY_ALL_ATTACKS=y 58 | # Immunity to Critical Hits does not confer immunity to sneak attack 59 | #export NWNX_TWEAKS_SNEAK_ATTACK_IGNORE_CRIT_IMMUNITY=y 60 | # Items are not destroyed when they reach 0 charges 61 | #export NWNX_TWEAKS_PRESERVE_DEPLETED_ITEMS=y 62 | # Fix some intel crashes by disabling some shadows on areas 63 | #export NWNX_TWEAKS_DISABLE_SHADOWS=y 64 | 65 | 66 | # Keep all logs in this directory 67 | LOGFILE=~/logs/mod-`date +%s`.txt 68 | echo "Starting $MODNAME. Log is $LOGFILE" 69 | 70 | # Set game options below 71 | 72 | export NWNX_CORE_LOAD_PATH=~/nwnx/Binaries 73 | LD_PRELOAD=~/nwnx/Binaries/NWNX_Core.so \ 74 | ./nwserver-linux \ 75 | -module "$MODNAME" \ 76 | -maxclients 96 \ 77 | -minlevel 1 \ 78 | -maxlevel 20 \ 79 | -pauseandplay 0 \ 80 | -pvp 2 \ 81 | -servervault 1 \ 82 | -elc 0 \ 83 | -ilr 0 \ 84 | -gametype 3 \ 85 | -oneparty 0 \ 86 | -difficulty 3 \ 87 | -autosaveinterval 0 \ 88 | -dmpassword 'dmpass' \ 89 | -servername 'myservername' \ 90 | -publicserver 1 \ 91 | -reloadwhenempty 0 \ 92 | -port 5121 \ 93 | "$@" >> $LOGFILE 2>&1 & 94 | 95 | echo $! > ~/.modpid 96 | popd 97 | -------------------------------------------------------------------------------- /nwnx-server-setup/mod-status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | status=0 4 | 5 | while read -r line 6 | do 7 | if output=$(ps -p "$line" >/dev/null 2>&1) 8 | then 9 | status=1 10 | fi 11 | done < ~/.modpid 12 | 13 | echo $status 14 | -------------------------------------------------------------------------------- /nwnx-server-setup/mod-stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Shutting down the server" 4 | cat ~/.modpid | xargs kill -9 5 | -------------------------------------------------------------------------------- /nwnx-server-setup/nwnx-setup.sh: -------------------------------------------------------------------------------- 1 | # NOTE: This is not a runnable file - you need to manually paste the lines one by one 2 | # Take some time to understand what each command does. 3 | # These steps were tested on a clean Ubuntu 18.04 Desktop install: 4 | 5 | # 6 | # Install necessary prereqs 7 | # 8 | # We use a new compiler which may not be available by default, so add an extra place to download packages from 9 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test 10 | # Redownload manifests for the newly added architecture and repository so we can use them 11 | sudo apt update 12 | # Upgrade any existing packages to the newest versions 13 | sudo apt upgrade 14 | # Install tools needed to build NWNX 15 | sudo apt install g++-7 g++-7-multilib gcc-7 gcc-7-multilib cmake git make unzip 16 | # Install stuff needed to build/run/use MySQL 17 | sudo apt install mysql-server libmysqlclient20 libmysqlclient-dev 18 | 19 | # 20 | # Download and build NWNX 21 | # 22 | # Get latest source from github 23 | git clone https://github.com/nwnxee/unified.git nwnx 24 | # Make a directory where the build system will initialize 25 | mkdir nwnx/build && cd nwnx/build 26 | # Initialize the build system to use GCC version 7. Build release version of nwnx, with debug info 27 | CC=gcc-7 CXX=g++-7 cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ../ 28 | # Build NWNX, in 6 threads. This will take a while 29 | make -j6 30 | 31 | # 32 | # Download NWN dedicated package 33 | # 34 | # Make a directory to hold NWN data 35 | mkdir ~/nwn && cd ~/nwn 36 | # Fetch the NWN dedicated server package. The version here might be outdated, so replace 8193.10 with current NWN build version 37 | wget https://nwnx.io/nwnee-dedicated-8193.10.zip 38 | # Unpack the server to current directory - ~/nwn 39 | unzip nwnee-dedicated-8193.10.zip -d . 40 | 41 | # Run it once to create the user directory. 42 | # nwserver must be run with current directory the same as the executable, so we need to `cd` into it first 43 | cd bin/linux-x86 && ./nwserver-linux 44 | # The user directory path is long and contains spaces, which is hard to type sometimes. 45 | # So we create a link (shortcut) to it as ~/nwn/userdir so it's easier to access 46 | ln -s ~/.local/share/Neverwinter\ Nights/ ~/nwn/userdir 47 | 48 | # Set up your module: 49 | # Copy your module/hak/tlk/etc files to ~/nwn/userdir 50 | # Edit ~/nwn/userdir/nwnplayer.ini to your preference 51 | 52 | # 53 | # Set up version control on the servervault 54 | # This is useful so you can restore player character backups if something goes wrong at any time 55 | # 56 | cd ~/nwn/userdir/servervault 57 | # We'll use git for version control since that's what NWNX uses. 58 | git init 59 | git config --global user.name = "My Name" 60 | git config --global user.email = "my@email.com" 61 | 62 | # 63 | # Set up the Database 64 | # 65 | # Run mysql (as admin/sudo). The following commands are given in MySQL, not the regular terminal 66 | sudo mysql 67 | # We want to configure mysql itself. 68 | mysql> USE mysql; 69 | # Create a new user for nwserver to use. 'nwn' is username, 'pass' is password, you can change them if you want. 70 | mysql> CREATE USER 'nwn'@'localhost' IDENTIFIED BY 'pass'; 71 | # Give it full access 72 | mysql> GRANT ALL PRIVILEGES ON *.* TO 'nwn'@'localhost'; 73 | mysql> FLUSH PRIVILEGES; 74 | # Create a database for the module to use, typically named same as module, but can be anything. 75 | mysql> CREATE DATABASE mymodulename; 76 | mysql> exit; 77 | 78 | # 79 | # Copy the scripts from this directory over onto ~/ 80 | # mod-start.sh - starts the server unless already running 81 | # mod-stop.sh - kills the server 82 | # mod-disable.sh - disables server auto restart 83 | # mod-enable.sh - enables server auto restart 84 | # mod-savechars.sh - saves servervault/ to git 85 | # mod-status.sh - returns 1 if server is running, 0 if not 86 | 87 | # Need to mark them as executable 88 | chmod +x mod-*.sh 89 | 90 | # Create a place where logs will be stored 91 | mkdir ~/logs 92 | 93 | # Edit the mod-start.sh script to further customize. You can use any other text editor instead. 94 | nano ~/mod-start.sh 95 | 96 | 97 | # 98 | # Set up cronjob for auto server restart every minute 99 | # 100 | # Cron lets you run some script at specified intervals. You need to run this command, and then add the line exactly as given below 101 | crontab -e 102 | # Add this line to the tab 103 | */1 * * * * ~/mod-start.sh 104 | 105 | # 106 | # Start the server. If it goes down, the cron job will restart it again within 1 minute. 107 | # 108 | ./mod-start.sh 109 | -------------------------------------------------------------------------------- /nwserver-dump-decode.c: -------------------------------------------------------------------------------- 1 | // Released under WTFPL-2.0 license 2 | // 3 | // nwserver stacktrace decoding tool: 4 | // - Decode a nwserver (linux or windows) stack trace to human readable symbols 5 | // 6 | // To compile, use any of: 7 | // make nwserver-dump-decode 8 | // cc -o nwserver-dump-decode nwserver-dump-decode.c 9 | // 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define HELP \ 16 | "NWN nwserver stacktrace decoding tool\n" \ 17 | "Usage: nwserver-dump-decode [OPTIONS]\n" \ 18 | "Options:\n" \ 19 | " -h, --help Print this help command\n" \ 20 | " -d, --dumpfile Path to the dump file to decode. Defaults to stdin if not specified\n" \ 21 | " -f, --funcfile Path to the functions.hpp file. Will attempt to auto detect if not specified\n" \ 22 | " -r, --repeat-input Will print all non-decoded input lines over to output.\n" \ 23 | " -a, --autodetect Try to automatically detect the \n" \ 24 | "\n" \ 25 | "Example usages:\n" \ 26 | " Decode a crash dump with autodetcting the offsets:\n" \ 27 | " nwserver-dump-decode -r -a < nwserver-crash-1543867203.log\n" \ 28 | " Decode a crash dump with manually specifying the offsets:\n" \ 29 | " nwserver-dump-decode -r -d nwserver-crash-1543867203.log -f ~/nwnx/NWNXLib/API/FunctionsLinux.hpp\n" 30 | 31 | 32 | #define die(format, ...) \ 33 | do { \ 34 | fprintf(stderr, format "\n", ##__VA_ARGS__); \ 35 | exit(~0); \ 36 | } while(0) 37 | 38 | struct args { 39 | int repeat; 40 | int autodetect; 41 | char *dumpfile; 42 | char *funcfile; 43 | } args; 44 | 45 | void parse_cmdline(int argc, char *argv[]) { 46 | for (int i = 1; i < argc; i++) { 47 | if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { 48 | printf(HELP); 49 | exit(0); 50 | } 51 | 52 | args.repeat |= !strcmp(argv[i], "-r") || !strcmp(argv[i], "--repeat-input"); 53 | args.autodetect |= !strcmp(argv[i], "-a") || !strcmp(argv[i], "--autodetect"); 54 | 55 | if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--dumpfile")) { 56 | if (i == argc-1) 57 | die("Bad argument - Need file name with -d / --dumpfile"); 58 | args.dumpfile = argv[++i]; 59 | } 60 | if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--funcfile")) { 61 | if (i == argc-1) 62 | die("Bad argument - Need file name with -f / --funcfile"); 63 | args.funcfile = argv[++i]; 64 | } 65 | } 66 | 67 | if (args.autodetect && args.funcfile) 68 | die("Bad arguments: --autodetect and --funcfile are mutually exclusive"); 69 | else if (!args.funcfile) 70 | args.autodetect = 1; 71 | } 72 | 73 | #define MAX_FUNCTIONS 10000 74 | struct function { 75 | char name[256]; 76 | uint32_t offset; 77 | } *functions; 78 | uint32_t fcount; 79 | 80 | int cmp(const void *a, const void *b) { 81 | const struct function *f1 = a, *f2 = b; 82 | return (int64_t)f1->offset - (int64_t)f2->offset; 83 | } 84 | 85 | void load_functions(const char *infile) { 86 | FILE *f = fopen(infile, "r"); 87 | if (!f) 88 | die("Input file '%s' not found", infile); 89 | 90 | functions = malloc(MAX_FUNCTIONS * sizeof(*functions)); 91 | if (!functions) 92 | die("Out of memory"); 93 | 94 | char buf[1024]; 95 | while (fgets(buf, 1024, f)) 96 | fcount += (sscanf(buf, "constexpr%*[ \t]uintptr_t%*[ \t]%s%*[ \t]=%*[ \t]%x;", functions[fcount].name, &functions[fcount].offset) == 2); 97 | 98 | fclose(f); 99 | 100 | qsort(functions, fcount, sizeof(*functions), cmp); 101 | } 102 | 103 | uint32_t lookup(uint32_t offset) { 104 | for (uint32_t i = 1; i < fcount; i++) { 105 | if (functions[i].offset > offset) { 106 | return i-1; 107 | } 108 | } 109 | return ~0; 110 | } 111 | char *decode(uint32_t offset) { 112 | static char out[1024]; 113 | uint32_t idx = lookup(offset); 114 | if (idx != ~0) { 115 | sprintf(out, "%s+0x%x", functions[idx].name, offset - functions[idx].offset); 116 | return out; 117 | } 118 | return NULL; 119 | } 120 | 121 | char *try_parse(char *buf) { 122 | static char out[1024]; 123 | char tmp[1024] = ""; 124 | uint32_t offset = 0; 125 | 126 | if (sscanf(buf, "%X", &offset)) { 127 | return decode(offset); 128 | } else if (sscanf(buf, "./nwserver-linux(+0x%x)%[^\n]", &offset, tmp)) { 129 | char *dec = decode(offset); 130 | if (dec) { 131 | sprintf(out, "./nwserver-linux(%s)%s", dec, tmp); 132 | return out; 133 | } 134 | } 135 | return NULL; 136 | } 137 | 138 | #define starts_with(str1, str2) (!strncmp(str1, str2, strlen(str2))) 139 | 140 | char *detect_functions_file(int build, int os) { 141 | static char out[1024]; 142 | static const char *paths[] = { 143 | "extra/offsets", 144 | "../extra/offsets", 145 | "../../extra/offsets", 146 | "offsets", 147 | "." 148 | }; 149 | static const char *filenames[] = { "FunctionsLinux", "FunctionsWindows" }; 150 | for (uint32_t i = 0; i < (sizeof(paths)/sizeof(paths[0])); i++) { 151 | sprintf(out, "%s/%s-%4d.hpp", paths[i], filenames[os], build); 152 | FILE *f = fopen(out, "r"); 153 | if (f) { 154 | fclose(f); 155 | return out; 156 | } 157 | } 158 | // Try to detect nwnx and use the current one.. 159 | static const char *nwnxpaths[] = { 160 | "~/nwnx", 161 | "~/unified", 162 | "~/nwnx/unified", 163 | "~/nwn/nwnx", 164 | "~/nwn/unified", 165 | "~/nwn/nwnx/unified", 166 | "../nwnx", 167 | "../unified", 168 | "../nwnx/unified", 169 | "../nwn/nwnx", 170 | "../nwn/unified", 171 | "../nwn/nwnx/unified", 172 | "../../nwnx", 173 | "../../unified", 174 | "../../nwnx/unified", 175 | "../../nwn/nwnx", 176 | "../../nwn/unified", 177 | "../../nwn/nwnx/unified", 178 | "." 179 | }; 180 | for (uint32_t i = 0; i < (sizeof(nwnxpaths)/sizeof(nwnxpaths[0])); i++) { 181 | sprintf(out, "%s/NWNXLib/API/%s.hpp", nwnxpaths[i], filenames[os]); 182 | FILE *f = fopen(out, "r"); 183 | if (f) { 184 | char buf[1024]; 185 | int nwnxbuild = 0; 186 | while (fgets(buf, 1024, f)) { 187 | if (sscanf(buf, "NWNX_EXPECT_VERSION(%d);", &nwnxbuild)) 188 | break; 189 | } 190 | fclose(f); 191 | if (nwnxbuild != build) 192 | die("Autodetect found NWNX at build %d, but need build %d", nwnxbuild, build); 193 | 194 | return out; 195 | } 196 | } 197 | die("Autodetect of functions file failed"); 198 | } 199 | 200 | int main(int argc, char *argv[]) 201 | { 202 | parse_cmdline(argc, argv); 203 | if (!args.autodetect) 204 | load_functions(args.funcfile); 205 | 206 | FILE *in = args.dumpfile ? fopen(args.dumpfile, "r") : stdin; 207 | if (!in) 208 | die("Unable to open input file '%s'", args.dumpfile); 209 | 210 | char buf[1024]; 211 | int skip = 0; 212 | int windows = 1; 213 | int build; 214 | 215 | while (fgets(buf, 1024, in)) { 216 | if (starts_with(buf, "=== ")) { 217 | skip = !starts_with(buf, "=== Backtrace"); 218 | } 219 | 220 | if (args.autodetect) { 221 | sscanf(buf, "g_sBuildNumber = %d", &build); 222 | if (starts_with(buf, "&GenericCrashHandler")) { 223 | if (starts_with(buf, "&GenericCrashHandler = 0x")) 224 | windows = 0; 225 | load_functions(detect_functions_file(build, windows)); 226 | } 227 | } 228 | 229 | char *parse = try_parse(buf); 230 | if (parse && !skip) { 231 | printf("%s\n", parse); 232 | } else if (args.repeat) { 233 | printf("%s", buf); 234 | } 235 | } 236 | 237 | return 0; 238 | } 239 | 240 | --------------------------------------------------------------------------------