Blame


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