1 1.26 rillig /* $NetBSD: scores.c,v 1.26 2021/05/02 12:50:46 rillig Exp $ */ 2 1.2 cgd 3 1.1 cgd /*- 4 1.1 cgd * Copyright (c) 1992, 1993 5 1.1 cgd * The Regents of the University of California. All rights reserved. 6 1.1 cgd * 7 1.1 cgd * This code is derived from software contributed to Berkeley by 8 1.1 cgd * Chris Torek and Darren F. Provine. 9 1.1 cgd * 10 1.1 cgd * Redistribution and use in source and binary forms, with or without 11 1.1 cgd * modification, are permitted provided that the following conditions 12 1.1 cgd * are met: 13 1.1 cgd * 1. Redistributions of source code must retain the above copyright 14 1.1 cgd * notice, this list of conditions and the following disclaimer. 15 1.1 cgd * 2. Redistributions in binary form must reproduce the above copyright 16 1.1 cgd * notice, this list of conditions and the following disclaimer in the 17 1.1 cgd * documentation and/or other materials provided with the distribution. 18 1.12 agc * 3. Neither the name of the University nor the names of its contributors 19 1.1 cgd * may be used to endorse or promote products derived from this software 20 1.1 cgd * without specific prior written permission. 21 1.1 cgd * 22 1.1 cgd * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 1.1 cgd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 1.1 cgd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 1.1 cgd * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 1.1 cgd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 1.1 cgd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 1.1 cgd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 1.1 cgd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 1.1 cgd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 1.1 cgd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 1.1 cgd * SUCH DAMAGE. 33 1.1 cgd * 34 1.1 cgd * @(#)scores.c 8.1 (Berkeley) 5/31/93 35 1.1 cgd */ 36 1.1 cgd 37 1.1 cgd /* 38 1.1 cgd * Score code for Tetris, by Darren Provine (kilroy (at) gboro.glassboro.edu) 39 1.1 cgd * modified 22 January 1992, to limit the number of entries any one 40 1.1 cgd * person has. 41 1.1 cgd * 42 1.1 cgd * Major whacks since then. 43 1.1 cgd */ 44 1.7 jsm #include <err.h> 45 1.1 cgd #include <errno.h> 46 1.1 cgd #include <fcntl.h> 47 1.1 cgd #include <pwd.h> 48 1.1 cgd #include <stdio.h> 49 1.1 cgd #include <stdlib.h> 50 1.1 cgd #include <string.h> 51 1.6 jsm #include <sys/stat.h> 52 1.1 cgd #include <time.h> 53 1.19 roy #include <term.h> 54 1.1 cgd #include <unistd.h> 55 1.1 cgd 56 1.1 cgd #include "pathnames.h" 57 1.1 cgd #include "screen.h" 58 1.1 cgd #include "scores.h" 59 1.1 cgd #include "tetris.h" 60 1.1 cgd 61 1.1 cgd /* 62 1.18 dholland * Allow updating the high scores unless we're built as part of /rescue. 63 1.18 dholland */ 64 1.18 dholland #ifndef RESCUEDIR 65 1.18 dholland #define ALLOW_SCORE_UPDATES 66 1.18 dholland #endif 67 1.18 dholland 68 1.18 dholland /* 69 1.1 cgd * Within this code, we can hang onto one extra "high score", leaving 70 1.1 cgd * room for our current score (whether or not it is high). 71 1.1 cgd * 72 1.1 cgd * We also sometimes keep tabs on the "highest" score on each level. 73 1.1 cgd * As long as the scores are kept sorted, this is simply the first one at 74 1.1 cgd * that level. 75 1.1 cgd */ 76 1.1 cgd #define NUMSPOTS (MAXHISCORES + 1) 77 1.1 cgd #define NLEVELS (MAXLEVEL + 1) 78 1.1 cgd 79 1.1 cgd static time_t now; 80 1.1 cgd static int nscores; 81 1.1 cgd static int gotscores; 82 1.1 cgd static struct highscore scores[NUMSPOTS]; 83 1.1 cgd 84 1.13 jsm static int checkscores(struct highscore *, int); 85 1.13 jsm static int cmpscores(const void *, const void *); 86 1.16 dholland static void getscores(int *); 87 1.13 jsm static void printem(int, int, struct highscore *, int, const char *); 88 1.13 jsm static char *thisuser(void); 89 1.1 cgd 90 1.16 dholland /* contents chosen to be a highly illegal username */ 91 1.16 dholland static const char hsh_magic_val[HSH_MAGIC_SIZE] = "//:\0\0://"; 92 1.16 dholland 93 1.16 dholland #define HSH_ENDIAN_NATIVE 0x12345678 94 1.16 dholland #define HSH_ENDIAN_OPP 0x78563412 95 1.16 dholland 96 1.16 dholland /* current file format version */ 97 1.16 dholland #define HSH_VERSION 1 98 1.16 dholland 99 1.16 dholland /* codes for scorefile_probe return */ 100 1.16 dholland #define SCOREFILE_ERROR (-1) 101 1.16 dholland #define SCOREFILE_CURRENT 0 /* 40-byte */ 102 1.16 dholland #define SCOREFILE_CURRENT_OPP 1 /* 40-byte, opposite-endian */ 103 1.16 dholland #define SCOREFILE_599 2 /* 36-byte */ 104 1.16 dholland #define SCOREFILE_599_OPP 3 /* 36-byte, opposite-endian */ 105 1.16 dholland #define SCOREFILE_50 4 /* 32-byte */ 106 1.16 dholland #define SCOREFILE_50_OPP 5 /* 32-byte, opposite-endian */ 107 1.16 dholland 108 1.16 dholland /* 109 1.16 dholland * Check (or guess) what kind of score file contents we have. 110 1.16 dholland */ 111 1.16 dholland static int 112 1.16 dholland scorefile_probe(int sd) 113 1.16 dholland { 114 1.16 dholland struct stat st; 115 1.16 dholland int t1, t2, t3, tx; 116 1.16 dholland ssize_t result; 117 1.16 dholland uint32_t numbers[3], offset56, offset60, offset64; 118 1.16 dholland 119 1.16 dholland if (fstat(sd, &st) < 0) { 120 1.16 dholland warn("Score file %s: fstat", _PATH_SCOREFILE); 121 1.16 dholland return -1; 122 1.16 dholland } 123 1.16 dholland 124 1.16 dholland t1 = st.st_size % sizeof(struct highscore_ondisk) == 0; 125 1.16 dholland t2 = st.st_size % sizeof(struct highscore_ondisk_599) == 0; 126 1.16 dholland t3 = st.st_size % sizeof(struct highscore_ondisk_50) == 0; 127 1.16 dholland tx = t1 + t2 + t3; 128 1.16 dholland if (tx == 1) { 129 1.16 dholland /* Size matches exact number of one kind of records */ 130 1.16 dholland if (t1) { 131 1.16 dholland return SCOREFILE_CURRENT; 132 1.16 dholland } else if (t2) { 133 1.16 dholland return SCOREFILE_599; 134 1.16 dholland } else { 135 1.16 dholland return SCOREFILE_50; 136 1.16 dholland } 137 1.16 dholland } else if (tx == 0) { 138 1.16 dholland /* Size matches nothing, pick most likely as default */ 139 1.16 dholland goto wildguess; 140 1.16 dholland } 141 1.16 dholland 142 1.16 dholland /* 143 1.16 dholland * File size is multiple of more than one structure size. 144 1.16 dholland * (For example, 288 bytes could be 9*hso50 or 8*hso599.) 145 1.16 dholland * Read the file and see if we can figure out what's going 146 1.16 dholland * on. This is the layout of the first two records: 147 1.16 dholland * 148 1.16 dholland * offset hso / current hso_599 hso_50 149 1.16 dholland * (40-byte) (36-byte) (32-byte) 150 1.16 dholland * 151 1.16 dholland * 0 name #0 name #0 name #0 152 1.16 dholland * 4 : : : 153 1.16 dholland * 8 : : : 154 1.16 dholland * 12 : : : 155 1.16 dholland * 16 : : : 156 1.16 dholland * 20 score #0 score #0 score #0 157 1.16 dholland * 24 level #0 level #0 level #0 158 1.16 dholland * 28 (pad) time #0 time #0 159 1.16 dholland * 32 time #0 name #1 160 1.16 dholland * 36 name #1 : 161 1.16 dholland * 40 name #1 : : 162 1.16 dholland * 44 : : : 163 1.16 dholland * 48 : : : 164 1.16 dholland * 52 : : score #1 165 1.16 dholland * 56 : score #1 level #1 166 1.16 dholland * 60 score #1 level #1 time #1 167 1.16 dholland * 64 level #1 time #1 name #2 168 1.16 dholland * 68 (pad) : : 169 1.16 dholland * 72 time #1 name #2 : 170 1.16 dholland * 76 : : : 171 1.16 dholland * 80 --- end --- 172 1.16 dholland * 173 1.16 dholland * There are a number of things we could check here, but the 174 1.16 dholland * most effective test is based on the following restrictions: 175 1.16 dholland * 176 1.16 dholland * - The level must be between 1 and 9 (inclusive) 177 1.16 dholland * - All times must be after 1985 and are before 2038, 178 1.16 dholland * so the high word must be 0 and the low word may not be 179 1.16 dholland * a small value. 180 1.16 dholland * - Integer values of 0 or 1-9 cannot be the beginning of 181 1.16 dholland * a login name string. 182 1.16 dholland * - Values of 1-9 are probably not a score. 183 1.16 dholland * 184 1.16 dholland * So we read the three words at offsets 56, 60, and 64, and 185 1.16 dholland * poke at the values to try to figure things... 186 1.16 dholland */ 187 1.16 dholland 188 1.16 dholland if (lseek(sd, 56, SEEK_SET) < 0) { 189 1.16 dholland warn("Score file %s: lseek", _PATH_SCOREFILE); 190 1.16 dholland return -1; 191 1.16 dholland } 192 1.16 dholland result = read(sd, &numbers, sizeof(numbers)); 193 1.16 dholland if (result < 0) { 194 1.16 dholland warn("Score file %s: read", _PATH_SCOREFILE); 195 1.16 dholland return -1; 196 1.16 dholland } 197 1.16 dholland if ((size_t)result != sizeof(numbers)) { 198 1.16 dholland /* 199 1.16 dholland * The smallest file whose size divides by more than 200 1.16 dholland * one of the sizes is substantially larger than 64, 201 1.16 dholland * so this should *never* happen. 202 1.16 dholland */ 203 1.16 dholland warnx("Score file %s: Unexpected EOF", _PATH_SCOREFILE); 204 1.16 dholland return -1; 205 1.16 dholland } 206 1.16 dholland 207 1.16 dholland offset56 = numbers[0]; 208 1.16 dholland offset60 = numbers[1]; 209 1.16 dholland offset64 = numbers[2]; 210 1.16 dholland 211 1.16 dholland if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) { 212 1.16 dholland /* 40-byte structure */ 213 1.16 dholland return SCOREFILE_CURRENT; 214 1.16 dholland } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) { 215 1.16 dholland /* 36-byte structure */ 216 1.16 dholland return SCOREFILE_599; 217 1.16 dholland } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) { 218 1.16 dholland /* 32-byte structure */ 219 1.16 dholland return SCOREFILE_50; 220 1.16 dholland } 221 1.16 dholland 222 1.16 dholland /* None was a valid level; try opposite endian */ 223 1.16 dholland offset64 = bswap32(offset64); 224 1.16 dholland offset60 = bswap32(offset60); 225 1.16 dholland offset56 = bswap32(offset56); 226 1.16 dholland 227 1.16 dholland if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) { 228 1.16 dholland /* 40-byte structure */ 229 1.16 dholland return SCOREFILE_CURRENT_OPP; 230 1.16 dholland } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) { 231 1.16 dholland /* 36-byte structure */ 232 1.16 dholland return SCOREFILE_599_OPP; 233 1.16 dholland } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) { 234 1.16 dholland /* 32-byte structure */ 235 1.16 dholland return SCOREFILE_50_OPP; 236 1.16 dholland } 237 1.16 dholland 238 1.16 dholland /* That didn't work either, dunno what's going on */ 239 1.16 dholland wildguess: 240 1.16 dholland warnx("Score file %s is likely corrupt", _PATH_SCOREFILE); 241 1.16 dholland if (sizeof(void *) == 8 && sizeof(time_t) == 8) { 242 1.16 dholland return SCOREFILE_CURRENT; 243 1.16 dholland } else if (sizeof(time_t) == 8) { 244 1.16 dholland return SCOREFILE_599; 245 1.16 dholland } else { 246 1.16 dholland return SCOREFILE_50; 247 1.16 dholland } 248 1.16 dholland } 249 1.16 dholland 250 1.16 dholland /* 251 1.16 dholland * Copy a string safely, making sure it's null-terminated. 252 1.16 dholland */ 253 1.16 dholland static void 254 1.16 dholland readname(char *to, size_t maxto, const char *from, size_t maxfrom) 255 1.16 dholland { 256 1.16 dholland size_t amt; 257 1.16 dholland 258 1.16 dholland amt = maxto < maxfrom ? maxto : maxfrom; 259 1.16 dholland memcpy(to, from, amt); 260 1.16 dholland to[maxto-1] = '\0'; 261 1.16 dholland } 262 1.16 dholland 263 1.16 dholland /* 264 1.16 dholland * Copy integers, byte-swapping if desired. 265 1.16 dholland */ 266 1.16 dholland static int32_t 267 1.16 dholland read32(int32_t val, int doflip) 268 1.16 dholland { 269 1.16 dholland if (doflip) { 270 1.16 dholland val = bswap32(val); 271 1.16 dholland } 272 1.16 dholland return val; 273 1.16 dholland } 274 1.16 dholland 275 1.16 dholland static int64_t 276 1.16 dholland read64(int64_t val, int doflip) 277 1.16 dholland { 278 1.16 dholland if (doflip) { 279 1.16 dholland val = bswap64(val); 280 1.16 dholland } 281 1.16 dholland return val; 282 1.16 dholland } 283 1.16 dholland 284 1.16 dholland /* 285 1.16 dholland * Read up to MAXHISCORES scorefile_ondisk entries. 286 1.16 dholland */ 287 1.16 dholland static int 288 1.16 dholland readscores(int sd, int doflip) 289 1.16 dholland { 290 1.16 dholland struct highscore_ondisk buf[MAXHISCORES]; 291 1.16 dholland ssize_t result; 292 1.16 dholland int i; 293 1.16 dholland 294 1.16 dholland result = read(sd, buf, sizeof(buf)); 295 1.16 dholland if (result < 0) { 296 1.16 dholland warn("Score file %s: read", _PATH_SCOREFILE); 297 1.16 dholland return -1; 298 1.16 dholland } 299 1.16 dholland nscores = result / sizeof(buf[0]); 300 1.16 dholland 301 1.16 dholland for (i=0; i<nscores; i++) { 302 1.16 dholland readname(scores[i].hs_name, sizeof(scores[i].hs_name), 303 1.16 dholland buf[i].hso_name, sizeof(buf[i].hso_name)); 304 1.16 dholland scores[i].hs_score = read32(buf[i].hso_score, doflip); 305 1.16 dholland scores[i].hs_level = read32(buf[i].hso_level, doflip); 306 1.16 dholland scores[i].hs_time = read64(buf[i].hso_time, doflip); 307 1.16 dholland } 308 1.16 dholland return 0; 309 1.16 dholland } 310 1.16 dholland 311 1.16 dholland /* 312 1.16 dholland * Read up to MAXHISCORES scorefile_ondisk_599 entries. 313 1.16 dholland */ 314 1.16 dholland static int 315 1.16 dholland readscores599(int sd, int doflip) 316 1.16 dholland { 317 1.16 dholland struct highscore_ondisk_599 buf[MAXHISCORES]; 318 1.16 dholland ssize_t result; 319 1.16 dholland int i; 320 1.16 dholland 321 1.16 dholland result = read(sd, buf, sizeof(buf)); 322 1.16 dholland if (result < 0) { 323 1.16 dholland warn("Score file %s: read", _PATH_SCOREFILE); 324 1.16 dholland return -1; 325 1.16 dholland } 326 1.16 dholland nscores = result / sizeof(buf[0]); 327 1.16 dholland 328 1.16 dholland for (i=0; i<nscores; i++) { 329 1.16 dholland readname(scores[i].hs_name, sizeof(scores[i].hs_name), 330 1.16 dholland buf[i].hso599_name, sizeof(buf[i].hso599_name)); 331 1.16 dholland scores[i].hs_score = read32(buf[i].hso599_score, doflip); 332 1.16 dholland scores[i].hs_level = read32(buf[i].hso599_level, doflip); 333 1.16 dholland /* 334 1.16 dholland * Don't bother pasting the time together into a 335 1.16 dholland * 64-bit value; just take whichever half is nonzero. 336 1.16 dholland */ 337 1.16 dholland scores[i].hs_time = 338 1.16 dholland read32(buf[i].hso599_time[buf[i].hso599_time[0] == 0], 339 1.16 dholland doflip); 340 1.16 dholland } 341 1.16 dholland return 0; 342 1.16 dholland } 343 1.16 dholland 344 1.16 dholland /* 345 1.16 dholland * Read up to MAXHISCORES scorefile_ondisk_50 entries. 346 1.16 dholland */ 347 1.16 dholland static int 348 1.16 dholland readscores50(int sd, int doflip) 349 1.16 dholland { 350 1.16 dholland struct highscore_ondisk_50 buf[MAXHISCORES]; 351 1.16 dholland ssize_t result; 352 1.16 dholland int i; 353 1.16 dholland 354 1.16 dholland result = read(sd, buf, sizeof(buf)); 355 1.16 dholland if (result < 0) { 356 1.16 dholland warn("Score file %s: read", _PATH_SCOREFILE); 357 1.16 dholland return -1; 358 1.16 dholland } 359 1.16 dholland nscores = result / sizeof(buf[0]); 360 1.16 dholland 361 1.16 dholland for (i=0; i<nscores; i++) { 362 1.16 dholland readname(scores[i].hs_name, sizeof(scores[i].hs_name), 363 1.16 dholland buf[i].hso50_name, sizeof(buf[i].hso50_name)); 364 1.16 dholland scores[i].hs_score = read32(buf[i].hso50_score, doflip); 365 1.16 dholland scores[i].hs_level = read32(buf[i].hso50_level, doflip); 366 1.16 dholland scores[i].hs_time = read32(buf[i].hso50_time, doflip); 367 1.16 dholland } 368 1.16 dholland return 0; 369 1.16 dholland } 370 1.16 dholland 371 1.1 cgd /* 372 1.1 cgd * Read the score file. Can be called from savescore (before showscores) 373 1.1 cgd * or showscores (if savescore will not be called). If the given pointer 374 1.16 dholland * is not NULL, sets *fdp to an open file handle that corresponds to a 375 1.1 cgd * read/write score file that is locked with LOCK_EX. Otherwise, the 376 1.1 cgd * file is locked with LOCK_SH for the read and closed before return. 377 1.1 cgd */ 378 1.1 cgd static void 379 1.16 dholland getscores(int *fdp) 380 1.1 cgd { 381 1.16 dholland struct highscore_header header; 382 1.1 cgd int sd, mint, lck; 383 1.6 jsm mode_t mask; 384 1.21 christos const char *human; 385 1.16 dholland int doflip; 386 1.22 dholland int serrno; 387 1.16 dholland ssize_t result; 388 1.1 cgd 389 1.18 dholland #ifdef ALLOW_SCORE_UPDATES 390 1.16 dholland if (fdp != NULL) { 391 1.1 cgd mint = O_RDWR | O_CREAT; 392 1.1 cgd human = "read/write"; 393 1.1 cgd lck = LOCK_EX; 394 1.18 dholland } else 395 1.18 dholland #endif 396 1.18 dholland { 397 1.1 cgd mint = O_RDONLY; 398 1.1 cgd human = "reading"; 399 1.1 cgd lck = LOCK_SH; 400 1.1 cgd } 401 1.6 jsm setegid(egid); 402 1.6 jsm mask = umask(S_IWOTH); 403 1.1 cgd sd = open(_PATH_SCOREFILE, mint, 0666); 404 1.22 dholland serrno = errno; 405 1.6 jsm (void)umask(mask); 406 1.16 dholland setegid(gid); 407 1.1 cgd if (sd < 0) { 408 1.16 dholland /* 409 1.16 dholland * If the file simply isn't there because nobody's 410 1.16 dholland * played yet, and we aren't going to be trying to 411 1.16 dholland * update it, don't warn. Even if we are going to be 412 1.16 dholland * trying to write it, don't fail -- we can still show 413 1.16 dholland * the player the score they got. 414 1.16 dholland */ 415 1.22 dholland errno = serrno; 416 1.16 dholland if (fdp != NULL || errno != ENOENT) { 417 1.16 dholland warn("Cannot open %s for %s", _PATH_SCOREFILE, human); 418 1.1 cgd } 419 1.16 dholland goto fail; 420 1.1 cgd } 421 1.1 cgd 422 1.1 cgd /* 423 1.1 cgd * Grab a lock. 424 1.16 dholland * XXX: failure here should probably be more fatal than this. 425 1.1 cgd */ 426 1.1 cgd if (flock(sd, lck)) 427 1.7 jsm warn("warning: score file %s cannot be locked", 428 1.7 jsm _PATH_SCOREFILE); 429 1.1 cgd 430 1.16 dholland /* 431 1.16 dholland * The current format (since -current of 20090525) is 432 1.16 dholland * 433 1.16 dholland * struct highscore_header 434 1.16 dholland * up to MAXHIGHSCORES x struct highscore_ondisk 435 1.16 dholland * 436 1.16 dholland * Before this, there is no header, and the contents 437 1.16 dholland * might be any of three formats: 438 1.16 dholland * 439 1.16 dholland * highscore_ondisk (64-bit machines with 64-bit time_t) 440 1.16 dholland * highscore_ondisk_599 (32-bit machines with 64-bit time_t) 441 1.16 dholland * highscore_ondisk_50 (32-bit machines with 32-bit time_t) 442 1.16 dholland * 443 1.16 dholland * The first two appear in 5.99 between the time_t change and 444 1.16 dholland * 20090525, depending on whether the compiler inserts 445 1.16 dholland * structure padding before an unaligned 64-bit time_t. The 446 1.16 dholland * last appears in 5.0 and earlier. 447 1.16 dholland * 448 1.16 dholland * Any or all of these might also appear on other OSes where 449 1.16 dholland * this code has been ported. 450 1.16 dholland * 451 1.16 dholland * Since the old file has no header, we will have to guess 452 1.16 dholland * which of these formats it has. 453 1.16 dholland */ 454 1.16 dholland 455 1.16 dholland /* 456 1.16 dholland * First, look for a header. 457 1.16 dholland */ 458 1.16 dholland result = read(sd, &header, sizeof(header)); 459 1.16 dholland if (result < 0) { 460 1.16 dholland warn("Score file %s: read", _PATH_SCOREFILE); 461 1.20 wiz goto sdfail; 462 1.16 dholland } 463 1.16 dholland if (result != 0 && (size_t)result != sizeof(header)) { 464 1.16 dholland warnx("Score file %s: read: unexpected EOF", _PATH_SCOREFILE); 465 1.16 dholland /* 466 1.16 dholland * File is hopelessly corrupt, might as well truncate it 467 1.16 dholland * and start over with empty scores. 468 1.16 dholland */ 469 1.16 dholland if (lseek(sd, 0, SEEK_SET) < 0) { 470 1.16 dholland /* ? */ 471 1.16 dholland warn("Score file %s: lseek", _PATH_SCOREFILE); 472 1.20 wiz goto sdfail; 473 1.16 dholland } 474 1.16 dholland if (ftruncate(sd, 0) == 0) { 475 1.16 dholland result = 0; 476 1.16 dholland } else { 477 1.20 wiz goto sdfail; 478 1.16 dholland } 479 1.16 dholland } 480 1.16 dholland 481 1.16 dholland if (result == 0) { 482 1.16 dholland /* Empty file; that just means there are no scores. */ 483 1.16 dholland nscores = 0; 484 1.16 dholland } else { 485 1.16 dholland /* 486 1.16 dholland * Is what we read a header, or the first 16 bytes of 487 1.16 dholland * a score entry? hsh_magic_val is chosen to be 488 1.16 dholland * something that is extremely unlikely to appear in 489 1.16 dholland * hs_name[]. 490 1.16 dholland */ 491 1.16 dholland if (!memcmp(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE)) { 492 1.16 dholland /* Yes, we have a header. */ 493 1.16 dholland 494 1.16 dholland if (header.hsh_endiantag == HSH_ENDIAN_NATIVE) { 495 1.16 dholland /* native endian */ 496 1.16 dholland doflip = 0; 497 1.16 dholland } else if (header.hsh_endiantag == HSH_ENDIAN_OPP) { 498 1.16 dholland doflip = 1; 499 1.16 dholland } else { 500 1.16 dholland warnx("Score file %s: Unknown endian tag %u", 501 1.16 dholland _PATH_SCOREFILE, header.hsh_endiantag); 502 1.20 wiz goto sdfail; 503 1.16 dholland } 504 1.16 dholland 505 1.16 dholland if (header.hsh_version != HSH_VERSION) { 506 1.16 dholland warnx("Score file %s: Unknown version code %u", 507 1.16 dholland _PATH_SCOREFILE, header.hsh_version); 508 1.20 wiz goto sdfail; 509 1.16 dholland } 510 1.16 dholland 511 1.16 dholland if (readscores(sd, doflip) < 0) { 512 1.20 wiz goto sdfail; 513 1.16 dholland } 514 1.16 dholland } else { 515 1.16 dholland /* 516 1.16 dholland * Ok, it wasn't a header. Try to figure out what 517 1.16 dholland * size records we have. 518 1.16 dholland */ 519 1.16 dholland result = scorefile_probe(sd); 520 1.16 dholland if (lseek(sd, 0, SEEK_SET) < 0) { 521 1.16 dholland warn("Score file %s: lseek", _PATH_SCOREFILE); 522 1.20 wiz goto sdfail; 523 1.16 dholland } 524 1.16 dholland switch (result) { 525 1.16 dholland case SCOREFILE_CURRENT: 526 1.16 dholland result = readscores(sd, 0 /* don't flip */); 527 1.16 dholland break; 528 1.16 dholland case SCOREFILE_CURRENT_OPP: 529 1.16 dholland result = readscores(sd, 1 /* do flip */); 530 1.16 dholland break; 531 1.16 dholland case SCOREFILE_599: 532 1.16 dholland result = readscores599(sd, 0 /* don't flip */); 533 1.16 dholland break; 534 1.16 dholland case SCOREFILE_599_OPP: 535 1.16 dholland result = readscores599(sd, 1 /* do flip */); 536 1.16 dholland break; 537 1.16 dholland case SCOREFILE_50: 538 1.16 dholland result = readscores50(sd, 0 /* don't flip */); 539 1.16 dholland break; 540 1.16 dholland case SCOREFILE_50_OPP: 541 1.16 dholland result = readscores50(sd, 1 /* do flip */); 542 1.16 dholland break; 543 1.16 dholland default: 544 1.20 wiz goto sdfail; 545 1.16 dholland } 546 1.16 dholland if (result < 0) { 547 1.20 wiz goto sdfail; 548 1.16 dholland } 549 1.16 dholland } 550 1.1 cgd } 551 1.26 rillig 552 1.1 cgd 553 1.16 dholland if (fdp) 554 1.16 dholland *fdp = sd; 555 1.1 cgd else 556 1.16 dholland close(sd); 557 1.16 dholland 558 1.16 dholland return; 559 1.16 dholland 560 1.20 wiz sdfail: 561 1.20 wiz close(sd); 562 1.16 dholland fail: 563 1.16 dholland if (fdp != NULL) { 564 1.16 dholland *fdp = -1; 565 1.16 dholland } 566 1.16 dholland nscores = 0; 567 1.16 dholland } 568 1.16 dholland 569 1.18 dholland #ifdef ALLOW_SCORE_UPDATES 570 1.16 dholland /* 571 1.16 dholland * Paranoid write wrapper; unlike fwrite() it preserves errno. 572 1.16 dholland */ 573 1.16 dholland static int 574 1.16 dholland dowrite(int sd, const void *vbuf, size_t len) 575 1.16 dholland { 576 1.16 dholland const char *buf = vbuf; 577 1.16 dholland ssize_t result; 578 1.16 dholland size_t done = 0; 579 1.16 dholland 580 1.16 dholland while (done < len) { 581 1.16 dholland result = write(sd, buf+done, len-done); 582 1.16 dholland if (result < 0) { 583 1.16 dholland if (errno == EINTR) { 584 1.16 dholland continue; 585 1.16 dholland } 586 1.16 dholland return -1; 587 1.16 dholland } 588 1.16 dholland done += result; 589 1.16 dholland } 590 1.16 dholland return 0; 591 1.1 cgd } 592 1.18 dholland #endif /* ALLOW_SCORE_UPDATES */ 593 1.1 cgd 594 1.16 dholland /* 595 1.16 dholland * Write the score file out. 596 1.16 dholland */ 597 1.16 dholland static void 598 1.16 dholland putscores(int sd) 599 1.16 dholland { 600 1.18 dholland #ifdef ALLOW_SCORE_UPDATES 601 1.16 dholland struct highscore_header header; 602 1.25 mrg struct highscore_ondisk buf[MAXHISCORES] = {0}; 603 1.16 dholland int i; 604 1.16 dholland 605 1.16 dholland if (sd == -1) { 606 1.16 dholland return; 607 1.16 dholland } 608 1.16 dholland 609 1.16 dholland memcpy(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE); 610 1.16 dholland header.hsh_endiantag = HSH_ENDIAN_NATIVE; 611 1.16 dholland header.hsh_version = HSH_VERSION; 612 1.16 dholland 613 1.16 dholland for (i=0; i<nscores; i++) { 614 1.25 mrg memcpy(buf[i].hso_name, scores[i].hs_name, 615 1.16 dholland sizeof(buf[i].hso_name)); 616 1.16 dholland buf[i].hso_score = scores[i].hs_score; 617 1.16 dholland buf[i].hso_level = scores[i].hs_level; 618 1.16 dholland buf[i].hso_pad = 0xbaadf00d; 619 1.16 dholland buf[i].hso_time = scores[i].hs_time; 620 1.16 dholland } 621 1.16 dholland 622 1.16 dholland if (lseek(sd, 0, SEEK_SET) < 0) { 623 1.16 dholland warn("Score file %s: lseek", _PATH_SCOREFILE); 624 1.16 dholland goto fail; 625 1.16 dholland } 626 1.16 dholland if (dowrite(sd, &header, sizeof(header)) < 0 || 627 1.16 dholland dowrite(sd, buf, sizeof(buf[0]) * nscores) < 0) { 628 1.16 dholland warn("Score file %s: write", _PATH_SCOREFILE); 629 1.16 dholland goto fail; 630 1.16 dholland } 631 1.16 dholland return; 632 1.16 dholland fail: 633 1.16 dholland warnx("high scores may be damaged"); 634 1.18 dholland #else 635 1.18 dholland (void)sd; 636 1.18 dholland #endif /* ALLOW_SCORE_UPDATES */ 637 1.16 dholland } 638 1.16 dholland 639 1.16 dholland /* 640 1.16 dholland * Close the score file. 641 1.16 dholland */ 642 1.16 dholland static void 643 1.16 dholland closescores(int sd) 644 1.16 dholland { 645 1.16 dholland flock(sd, LOCK_UN); 646 1.16 dholland close(sd); 647 1.16 dholland } 648 1.16 dholland 649 1.16 dholland /* 650 1.16 dholland * Read and update the scores file with the current reults. 651 1.16 dholland */ 652 1.1 cgd void 653 1.15 dholland savescore(int level) 654 1.1 cgd { 655 1.11 wiz struct highscore *sp; 656 1.11 wiz int i; 657 1.1 cgd int change; 658 1.16 dholland int sd; 659 1.1 cgd const char *me; 660 1.1 cgd 661 1.16 dholland getscores(&sd); 662 1.1 cgd gotscores = 1; 663 1.1 cgd (void)time(&now); 664 1.1 cgd 665 1.1 cgd /* 666 1.1 cgd * Allow at most one score per person per level -- see if we 667 1.1 cgd * can replace an existing score, or (easiest) do nothing. 668 1.1 cgd * Otherwise add new score at end (there is always room). 669 1.1 cgd */ 670 1.1 cgd change = 0; 671 1.1 cgd me = thisuser(); 672 1.1 cgd for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) { 673 1.1 cgd if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0) 674 1.1 cgd continue; 675 1.1 cgd if (score > sp->hs_score) { 676 1.1 cgd (void)printf("%s bettered %s %d score of %d!\n", 677 1.1 cgd "\nYou", "your old level", level, 678 1.1 cgd sp->hs_score * sp->hs_level); 679 1.1 cgd sp->hs_score = score; /* new score */ 680 1.1 cgd sp->hs_time = now; /* and time */ 681 1.1 cgd change = 1; 682 1.1 cgd } else if (score == sp->hs_score) { 683 1.1 cgd (void)printf("%s tied %s %d high score.\n", 684 1.1 cgd "\nYou", "your old level", level); 685 1.1 cgd sp->hs_time = now; /* renew it */ 686 1.1 cgd change = 1; /* gotta rewrite, sigh */ 687 1.1 cgd } /* else new score < old score: do nothing */ 688 1.1 cgd break; 689 1.1 cgd } 690 1.1 cgd if (i >= nscores) { 691 1.1 cgd strcpy(sp->hs_name, me); 692 1.1 cgd sp->hs_level = level; 693 1.1 cgd sp->hs_score = score; 694 1.1 cgd sp->hs_time = now; 695 1.1 cgd nscores++; 696 1.1 cgd change = 1; 697 1.1 cgd } 698 1.1 cgd 699 1.1 cgd if (change) { 700 1.1 cgd /* 701 1.1 cgd * Sort & clean the scores, then rewrite. 702 1.1 cgd */ 703 1.1 cgd nscores = checkscores(scores, nscores); 704 1.16 dholland putscores(sd); 705 1.1 cgd } 706 1.16 dholland closescores(sd); 707 1.1 cgd } 708 1.1 cgd 709 1.1 cgd /* 710 1.1 cgd * Get login name, or if that fails, get something suitable. 711 1.1 cgd * The result is always trimmed to fit in a score. 712 1.1 cgd */ 713 1.1 cgd static char * 714 1.15 dholland thisuser(void) 715 1.1 cgd { 716 1.11 wiz const char *p; 717 1.11 wiz struct passwd *pw; 718 1.11 wiz size_t l; 719 1.1 cgd static char u[sizeof(scores[0].hs_name)]; 720 1.1 cgd 721 1.1 cgd if (u[0]) 722 1.1 cgd return (u); 723 1.1 cgd p = getlogin(); 724 1.1 cgd if (p == NULL || *p == '\0') { 725 1.1 cgd pw = getpwuid(getuid()); 726 1.1 cgd if (pw != NULL) 727 1.1 cgd p = pw->pw_name; 728 1.1 cgd else 729 1.1 cgd p = " ???"; 730 1.1 cgd } 731 1.1 cgd l = strlen(p); 732 1.1 cgd if (l >= sizeof(u)) 733 1.1 cgd l = sizeof(u) - 1; 734 1.3 tls memcpy(u, p, l); 735 1.1 cgd u[l] = '\0'; 736 1.1 cgd return (u); 737 1.1 cgd } 738 1.1 cgd 739 1.1 cgd /* 740 1.1 cgd * Score comparison function for qsort. 741 1.1 cgd * 742 1.1 cgd * If two scores are equal, the person who had the score first is 743 1.1 cgd * listed first in the highscore file. 744 1.1 cgd */ 745 1.1 cgd static int 746 1.15 dholland cmpscores(const void *x, const void *y) 747 1.1 cgd { 748 1.11 wiz const struct highscore *a, *b; 749 1.11 wiz long l; 750 1.1 cgd 751 1.1 cgd a = x; 752 1.1 cgd b = y; 753 1.1 cgd l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score; 754 1.1 cgd if (l < 0) 755 1.1 cgd return (-1); 756 1.1 cgd if (l > 0) 757 1.1 cgd return (1); 758 1.1 cgd if (a->hs_time < b->hs_time) 759 1.1 cgd return (-1); 760 1.1 cgd if (a->hs_time > b->hs_time) 761 1.1 cgd return (1); 762 1.1 cgd return (0); 763 1.1 cgd } 764 1.1 cgd 765 1.1 cgd /* 766 1.1 cgd * If we've added a score to the file, we need to check the file and ensure 767 1.1 cgd * that this player has only a few entries. The number of entries is 768 1.1 cgd * controlled by MAXSCORES, and is to ensure that the highscore file is not 769 1.1 cgd * monopolised by just a few people. People who no longer have accounts are 770 1.1 cgd * only allowed the highest score. Scores older than EXPIRATION seconds are 771 1.1 cgd * removed, unless they are someone's personal best. 772 1.1 cgd * Caveat: the highest score on each level is always kept. 773 1.1 cgd */ 774 1.1 cgd static int 775 1.15 dholland checkscores(struct highscore *hs, int num) 776 1.1 cgd { 777 1.11 wiz struct highscore *sp; 778 1.11 wiz int i, j, k, numnames; 779 1.1 cgd int levelfound[NLEVELS]; 780 1.1 cgd struct peruser { 781 1.1 cgd char *name; 782 1.1 cgd int times; 783 1.1 cgd } count[NUMSPOTS]; 784 1.11 wiz struct peruser *pu; 785 1.1 cgd 786 1.1 cgd /* 787 1.1 cgd * Sort so that highest totals come first. 788 1.1 cgd * 789 1.1 cgd * levelfound[i] becomes set when the first high score for that 790 1.1 cgd * level is encountered. By definition this is the highest score. 791 1.1 cgd */ 792 1.1 cgd qsort((void *)hs, nscores, sizeof(*hs), cmpscores); 793 1.1 cgd for (i = MINLEVEL; i < NLEVELS; i++) 794 1.1 cgd levelfound[i] = 0; 795 1.1 cgd numnames = 0; 796 1.1 cgd for (i = 0, sp = hs; i < num;) { 797 1.1 cgd /* 798 1.1 cgd * This is O(n^2), but do you think we care? 799 1.1 cgd */ 800 1.1 cgd for (j = 0, pu = count; j < numnames; j++, pu++) 801 1.1 cgd if (strcmp(sp->hs_name, pu->name) == 0) 802 1.1 cgd break; 803 1.1 cgd if (j == numnames) { 804 1.1 cgd /* 805 1.1 cgd * Add new user, set per-user count to 1. 806 1.1 cgd */ 807 1.1 cgd pu->name = sp->hs_name; 808 1.1 cgd pu->times = 1; 809 1.1 cgd numnames++; 810 1.1 cgd } else { 811 1.1 cgd /* 812 1.1 cgd * Two ways to keep this score: 813 1.1 cgd * - Not too many (per user), still has acct, & 814 1.1 cgd * score not dated; or 815 1.1 cgd * - High score on this level. 816 1.1 cgd */ 817 1.1 cgd if ((pu->times < MAXSCORES && 818 1.1 cgd getpwnam(sp->hs_name) != NULL && 819 1.1 cgd sp->hs_time + EXPIRATION >= now) || 820 1.1 cgd levelfound[sp->hs_level] == 0) 821 1.1 cgd pu->times++; 822 1.1 cgd else { 823 1.1 cgd /* 824 1.1 cgd * Delete this score, do not count it, 825 1.1 cgd * do not pass go, do not collect $200. 826 1.1 cgd */ 827 1.1 cgd num--; 828 1.1 cgd for (k = i; k < num; k++) 829 1.1 cgd hs[k] = hs[k + 1]; 830 1.1 cgd continue; 831 1.1 cgd } 832 1.1 cgd } 833 1.24 mrg if (sp->hs_level < NLEVELS && sp->hs_level >= 0) 834 1.24 mrg levelfound[sp->hs_level] = 1; 835 1.1 cgd i++, sp++; 836 1.1 cgd } 837 1.1 cgd return (num > MAXHISCORES ? MAXHISCORES : num); 838 1.1 cgd } 839 1.1 cgd 840 1.1 cgd /* 841 1.1 cgd * Show current scores. This must be called after savescore, if 842 1.1 cgd * savescore is called at all, for two reasons: 843 1.1 cgd * - Showscores munches the time field. 844 1.1 cgd * - Even if that were not the case, a new score must be recorded 845 1.1 cgd * before it can be shown anyway. 846 1.1 cgd */ 847 1.1 cgd void 848 1.15 dholland showscores(int level) 849 1.1 cgd { 850 1.11 wiz struct highscore *sp; 851 1.11 wiz int i, n, c; 852 1.1 cgd const char *me; 853 1.1 cgd int levelfound[NLEVELS]; 854 1.1 cgd 855 1.1 cgd if (!gotscores) 856 1.16 dholland getscores(NULL); 857 1.1 cgd (void)printf("\n\t\t\t Tetris High Scores\n"); 858 1.1 cgd 859 1.1 cgd /* 860 1.1 cgd * If level == 0, the person has not played a game but just asked for 861 1.1 cgd * the high scores; we do not need to check for printing in highlight 862 1.1 cgd * mode. If SOstr is null, we can't do highlighting anyway. 863 1.1 cgd */ 864 1.19 roy me = level && enter_standout_mode ? thisuser() : NULL; 865 1.1 cgd 866 1.1 cgd /* 867 1.1 cgd * Set times to 0 except for high score on each level. 868 1.1 cgd */ 869 1.1 cgd for (i = MINLEVEL; i < NLEVELS; i++) 870 1.1 cgd levelfound[i] = 0; 871 1.1 cgd for (i = 0, sp = scores; i < nscores; i++, sp++) { 872 1.14 drochner if (sp->hs_level < NLEVELS && sp->hs_level >= 0) { 873 1.14 drochner if (levelfound[sp->hs_level]) 874 1.14 drochner sp->hs_time = 0; 875 1.14 drochner else { 876 1.14 drochner sp->hs_time = 1; 877 1.14 drochner levelfound[sp->hs_level] = 1; 878 1.14 drochner } 879 1.14 drochner } 880 1.1 cgd } 881 1.1 cgd 882 1.1 cgd /* 883 1.1 cgd * Page each screenful of scores. 884 1.1 cgd */ 885 1.1 cgd for (i = 0, sp = scores; i < nscores; sp += n) { 886 1.1 cgd n = 40; 887 1.1 cgd if (i + n > nscores) 888 1.1 cgd n = nscores - i; 889 1.1 cgd printem(level, i + 1, sp, n, me); 890 1.1 cgd if ((i += n) < nscores) { 891 1.1 cgd (void)printf("\nHit RETURN to continue."); 892 1.1 cgd (void)fflush(stdout); 893 1.1 cgd while ((c = getchar()) != '\n') 894 1.1 cgd if (c == EOF) 895 1.1 cgd break; 896 1.1 cgd (void)printf("\n"); 897 1.1 cgd } 898 1.1 cgd } 899 1.1 cgd } 900 1.1 cgd 901 1.1 cgd static void 902 1.15 dholland printem(int level, int offset, struct highscore *hs, int n, const char *me) 903 1.1 cgd { 904 1.11 wiz struct highscore *sp; 905 1.1 cgd int nrows, row, col, item, i, highlight; 906 1.1 cgd char buf[100]; 907 1.1 cgd #define TITLE "Rank Score Name (points/level)" 908 1.1 cgd 909 1.1 cgd /* 910 1.1 cgd * This makes a nice two-column sort with headers, but it's a bit 911 1.1 cgd * convoluted... 912 1.1 cgd */ 913 1.1 cgd printf("%s %s\n", TITLE, n > 1 ? TITLE : ""); 914 1.1 cgd 915 1.1 cgd highlight = 0; 916 1.1 cgd nrows = (n + 1) / 2; 917 1.1 cgd 918 1.1 cgd for (row = 0; row < nrows; row++) { 919 1.1 cgd for (col = 0; col < 2; col++) { 920 1.1 cgd item = col * nrows + row; 921 1.1 cgd if (item >= n) { 922 1.1 cgd /* 923 1.1 cgd * Can only occur on trailing columns. 924 1.1 cgd */ 925 1.1 cgd (void)putchar('\n'); 926 1.1 cgd continue; 927 1.1 cgd } 928 1.1 cgd sp = &hs[item]; 929 1.14 drochner (void)snprintf(buf, sizeof(buf), 930 1.9 jsm "%3d%c %6d %-11s (%6d on %d)", 931 1.1 cgd item + offset, sp->hs_time ? '*' : ' ', 932 1.1 cgd sp->hs_score * sp->hs_level, 933 1.1 cgd sp->hs_name, sp->hs_score, sp->hs_level); 934 1.1 cgd /* 935 1.1 cgd * Highlight if appropriate. This works because 936 1.1 cgd * we only get one score per level. 937 1.1 cgd */ 938 1.1 cgd if (me != NULL && 939 1.1 cgd sp->hs_level == level && 940 1.1 cgd sp->hs_score == score && 941 1.1 cgd strcmp(sp->hs_name, me) == 0) { 942 1.19 roy putpad(enter_standout_mode); 943 1.1 cgd highlight = 1; 944 1.1 cgd } 945 1.1 cgd (void)printf("%s", buf); 946 1.1 cgd if (highlight) { 947 1.19 roy putpad(exit_standout_mode); 948 1.1 cgd highlight = 0; 949 1.1 cgd } 950 1.1 cgd 951 1.1 cgd /* fill in spaces so column 1 lines up */ 952 1.1 cgd if (col == 0) 953 1.1 cgd for (i = 40 - strlen(buf); --i >= 0;) 954 1.1 cgd (void)putchar(' '); 955 1.1 cgd else /* col == 1 */ 956 1.1 cgd (void)putchar('\n'); 957 1.1 cgd } 958 1.1 cgd } 959 1.1 cgd } 960