1 /////////////////////////////////////////////////////////////////////////////
2 //
3 // filecache - code for background file reading/caching
4 //
5 /////////////////////////////////////////////////////////////////////////////
6
7 #include <stdio.h>
8 #include <string.h>
9 #include "utils.h"
10 #include "packfile.h"
11 #include "filecache.h"
12 #include "openbor.h"
13
14 #ifdef PSP
15 #include <pspsuspend.h>
16 #endif
17
18 #ifndef XBOX
19 #include <unistd.h>
20 #endif
21
22 #ifdef PS2
23 #include <eekernel.h>
24 #include <sifdev.h>
25 #endif
26
27 /////////////////////////////////////////////////////////////////////////////
28
29 static int filecache_blocksize;
30 static int filecache_blocks;
31 static int max_vfds;
32
33 //static int default_minimum_run_bytes = 98304;
34 static int default_minimum_run_bytes = 131072;
35 //static int default_minimum_run_bytes = 262144;
36
37 /////////////////////////////////////////////////////////////////////////////
38
39 // fd for pak file
40 // lba for pak file (if negative)
41 static int real_pakfd;
42
43 // total number of blocks in the pak
44 static int total_pakblocks;
45
46 // BLOCKSIZE * BLOCKS; be sure to 64-byte-align
47 static unsigned char *filecache;
48 static unsigned char *filecache_head = NULL;
49
50 // which pakblock is cached in each cacheblock?
51 // -1 means invalid
52 static int *filecache_pakmap;
53
54 // where is this pakblock cached?
55 // a value of FILECACHE_BLOCKS (255) means not cached.
56 // one byte for every block in the entire pak.
57 static unsigned char *where_is_this_pakblock_cached;
58
59 /////////////////////////////////////////////////////////////////////////////
60
61 // one per cacheblock
62 static unsigned *cacheblock_mru;
63
64 static unsigned cacheblock_lastused = 0;
65 static unsigned cacheblock_mru_counter = 0;
66
cacheblock_mark_used(unsigned n)67 static void cacheblock_mark_used(unsigned n)
68 {
69 if(n >= filecache_blocks) return;
70 if(n == cacheblock_lastused) return;
71 if(cacheblock_mru_counter == 0xFFFFFFFF)
72 {
73 unsigned i;
74 for(i = 0; i < filecache_blocks; i++) { cacheblock_mru[i] >>= 1; }
75 cacheblock_mru_counter >>= 1;
76 }
77 cacheblock_mru[n] = ++cacheblock_mru_counter;
78 cacheblock_lastused = n;
79 }
80
81 /////////////////////////////////////////////////////////////////////////////
82
83 // current read pointer (round down)
84 // -1 if not open
85 static int *vfd_readptr_pakblock;
86 // desired readahead in blocks
87 static int *vfd_desired_readahead_blocks;
88 // starting block of each open vfd
89 // these blocks are immune to being replaced in the cache
90 static int *vfd_startptr_pakblock;
91
92 // THIS IS TEMPORARY; DO NOT RELY ON THIS VALUE
93 static int *vfd_blocks_available;
94
95 // requested read block
96 static int request_read_pakblock = -1;
97
98 // avoid going off the end of the track for gdroms
99 static int filecache_maxcdsectors;
100
101 /////////////////////////////////////////////////////////////////////////////
102 //
103 // make sure that the total of all desired readahead is _less_ than the cache size!
104 // just to make things work more smoothly
105 //
106 // when finding a freeable cacheblock, select the one with the greatest extraneity
107 // defined as the greatest distance past the desired readahead for any vfd
108 //
109
110 /////////////////////////////////////////////////////////////////////////////
111 //
112 // find which cacheblock is the least useful
113 // this means: least recently used, and is not immune
114 //
find_least_useful_cacheblock(void)115 int find_least_useful_cacheblock(void)
116 {
117 int i, vfd;
118 int leastcacheblock = -1;
119 for(i = 0; i < filecache_blocks; i++)
120 {
121 int pakblock = filecache_pakmap[i];
122 if(pakblock < 0) return i;
123 // start and read pointers of any open files are both immune
124 for(vfd = 0; vfd < max_vfds; vfd++)
125 {
126 if(vfd_readptr_pakblock[vfd] < 0) continue;
127 if(vfd_startptr_pakblock[vfd] == pakblock) break;
128 if(vfd_readptr_pakblock[vfd] == pakblock) break;
129 if(vfd_desired_readahead_blocks[vfd] > 0) {
130 if((pakblock >= vfd_readptr_pakblock[vfd]) &&
131 (pakblock < (vfd_readptr_pakblock[vfd] + vfd_desired_readahead_blocks[vfd]))) break;
132 }
133 }
134 if(vfd < max_vfds) continue;
135 if((leastcacheblock < 0) ||
136 (cacheblock_mru[i] < cacheblock_mru[leastcacheblock])) { leastcacheblock = i; }
137 }
138 if(leastcacheblock < 0) leastcacheblock = 0;
139 return leastcacheblock;
140 }
141
142 /////////////////////////////////////////////////////////////////////////////
143 //
144 // get the number of blocks available for this vfd
145 // (short loop)
146 //
get_vfd_blocks_available(int vfd)147 static int get_vfd_blocks_available(int vfd)
148 {
149 int i;
150 // can't have more blocks than what exists in the cache
151 int max = filecache_blocks;
152 int ptr = vfd_readptr_pakblock[vfd];
153 if(ptr < 0) return 0; // no blocks available for a vfd that doesn't exist
154 max += ptr;
155 if(max > total_pakblocks) { max = total_pakblocks; }
156 for(i = ptr; i < max; i++) if(where_is_this_pakblock_cached[i] >= filecache_blocks) break;
157 return i - ptr;
158 }
159
160 /////////////////////////////////////////////////////////////////////////////
161 //
162 // top priority: emergency stream reads
163 // any vfds with desired readahead > 0, and less than (some number) blocks available
164 // the vfd with the least available blocks is serviced first
165 // normal priority: blocks needed for read calls
166 // any vfd blocked on a read call (must have 0 blocks available) is serviced
167 // ideally this is a first-come first-serve queue, but they can probably just
168 // be serviced in any order
169 // low priority: stream reads
170 // any vfds with desired readahead > 0, and less than that many bytes available
171 // the vfd with the least available blocks is serviced first
172 //
which_pakblock_to_read(int * suggested_min_run)173 int which_pakblock_to_read(int *suggested_min_run)
174 {
175 int vfd;
176 int least_avail;
177 int percent_available;
178 int least_percent_available;
179 int pakblock;
180
181 if(suggested_min_run) *suggested_min_run = default_minimum_run_bytes / filecache_blocksize;
182
183 // top priority: emergency stream reads
184 // any vfds with desired readahead > 0, and less than 1/4 blocks available
185 // the vfd with the least available blocks is serviced first
186 least_avail = -1;
187 least_percent_available = 100;
188 for(vfd = 0; vfd < max_vfds; vfd++)
189 {
190 if(vfd_readptr_pakblock[vfd] >= 0 && vfd_desired_readahead_blocks[vfd] > 0)
191 {
192 percent_available = (100*vfd_blocks_available[vfd])/vfd_desired_readahead_blocks[vfd];
193 if(percent_available < 25 && percent_available < least_percent_available)
194 {
195 pakblock = vfd_readptr_pakblock[vfd] + vfd_blocks_available[vfd];
196 if(pakblock >= 0 && pakblock < total_pakblocks)
197 {
198 least_percent_available = percent_available;
199 least_avail = vfd;
200 }
201 }
202 }
203 }
204 if(least_avail >= 0)
205 {
206 if(suggested_min_run) *suggested_min_run = vfd_desired_readahead_blocks[vfd] / 4;
207 return vfd_readptr_pakblock[least_avail] + vfd_blocks_available[least_avail];
208 }
209
210 // normal priority: blocks needed for read calls
211 // any vfd blocked on a read call (must have 0 blocks available) is serviced
212 // ideally this is a first-come first-serve queue, but they can probably just
213 // be serviced in any order
214 if(request_read_pakblock >= 0) return request_read_pakblock;
215
216 // low priority: stream reads
217 // any vfds with desired readahead > 0, and less than that many bytes available
218 // the vfd with the least available blocks is serviced first
219 least_avail = -1;
220 least_percent_available = 100;
221 for(vfd = 0; vfd < max_vfds; vfd++)
222 {
223 if(vfd_readptr_pakblock[vfd] >= 0 && vfd_desired_readahead_blocks[vfd] > 0)
224 {
225 percent_available = (100*vfd_blocks_available[vfd])/vfd_desired_readahead_blocks[vfd];
226 if(percent_available < least_percent_available)
227 {
228 pakblock = vfd_readptr_pakblock[vfd] + vfd_blocks_available[vfd];
229 if(pakblock >= 0 && pakblock < total_pakblocks)
230 {
231 least_percent_available = percent_available;
232 least_avail = vfd;
233 }
234 }
235 }
236 }
237 if(least_avail >= 0)
238 {
239 return vfd_readptr_pakblock[least_avail] + vfd_blocks_available[least_avail];
240 }
241
242 // nothing needed to read
243 return -1;
244 }
245
246 /////////////////////////////////////////////////////////////////////////////
247
248 static int last_cacheblock_read = -1;
249 static int last_pakblock_read = -1;
250
251 static int filecache_ready = 0;
252
253 static int pakblock_run_ptr = 0;
254 static int pakblock_run_len = 0;
255 static int pakblock_run_min = 1;
256
filecache_process(void)257 void filecache_process(void)
258 {
259 int vfd;
260 int least_useful_cacheblock;
261 int cacheblock_read;
262 int pakblock_read;
263
264 #ifdef PS2
265 int busy;
266 #endif
267
268 if(!filecache_ready) return;
269
270 #ifdef PS2
271 busy = 1;
272 sceIoctl(real_pakfd, SCE_FS_EXECUTING, &busy);
273 if(busy) return;
274 #elif DC
275 // busy?
276 if(real_pakfd < 0)
277 {
278 if(gdrom_poll()) return;
279 }
280 #endif
281
282 cacheblock_read = last_cacheblock_read;
283 pakblock_read = last_pakblock_read;
284
285 // if we just updated the cache, reflect the new changes
286 if(cacheblock_read >= 0 && pakblock_read >= 0)
287 {
288 filecache_pakmap[cacheblock_read] = pakblock_read;
289 where_is_this_pakblock_cached[pakblock_read] = cacheblock_read;
290 cacheblock_mark_used(cacheblock_read);
291 cacheblock_read = -1;
292 pakblock_read = -1;
293 }
294
295 // make sure request_read_pakblock isn't out of range
296 if(request_read_pakblock >= total_pakblocks) request_read_pakblock = 0;
297
298 // if the requested read block is available, signal so
299 if(request_read_pakblock >= 0 && where_is_this_pakblock_cached[request_read_pakblock] < filecache_blocks)
300 {
301 request_read_pakblock = -1;
302 }
303
304 // get the least useful cacheblock
305 least_useful_cacheblock = find_least_useful_cacheblock();
306
307 // get how many blocks are available to each vfd
308 for(vfd = 0; vfd < max_vfds; vfd++)
309 {
310 vfd_blocks_available[vfd] = get_vfd_blocks_available(vfd);
311 }
312
313 //
314 // now decide what pakblock to read next
315 //
316 if(pakblock_run_len >= pakblock_run_min) { pakblock_run_len = 0; }
317 if((pakblock_run_len > 0) && ((pakblock_run_ptr+1) < total_pakblocks))
318 {
319 pakblock_run_len++;
320 pakblock_read = ++pakblock_run_ptr;
321 }
322 else
323 {
324 int mymin = default_minimum_run_bytes / filecache_blocksize;
325 pakblock_run_min = mymin;
326 pakblock_run_len = 1;
327 pakblock_read = which_pakblock_to_read(&pakblock_run_min);
328 pakblock_run_ptr = pakblock_read;
329 if(pakblock_run_min < mymin) pakblock_run_min = mymin;
330 if(pakblock_read < 0) pakblock_run_len = 0;
331 }
332
333 //
334 // nullify pakblock_read if it's out of bounds or already cached
335 //
336 // if pakblock_read is out of range, nullify it
337 if(pakblock_read >= 0) { if(pakblock_read >= total_pakblocks) { pakblock_read = -1; } }
338 // if the pakblock is already cached, don't read it!
339 if(pakblock_read >= 0 && where_is_this_pakblock_cached[pakblock_read] < filecache_blocks) { pakblock_read = -1; }
340
341 // if we're reading a pakblock, read it into the least useful cacheblock
342 // and invalidate that part of the cache
343 if(pakblock_read >= 0)
344 {
345 int oldpak;
346 cacheblock_read = least_useful_cacheblock;
347 oldpak = filecache_pakmap[cacheblock_read];
348 if(oldpak >= 0 && oldpak < total_pakblocks)
349 {
350 where_is_this_pakblock_cached[oldpak] = filecache_blocks;
351 }
352 filecache_pakmap[cacheblock_read] = -1;
353 }
354 else
355 {
356 cacheblock_read = -1;
357 }
358
359 last_pakblock_read = pakblock_read;
360 last_cacheblock_read = cacheblock_read;
361
362 // if we wanted to read something, read it
363 if(pakblock_read >= 0 && cacheblock_read >= 0)
364 {
365 #ifdef DC
366 if(real_pakfd < 0)
367 {
368 void *dest = filecache + (cacheblock_read * filecache_blocksize);
369 int lba = (pakblock_read * filecache_blocksize) / 2048;
370 int n = filecache_blocksize / 2048;
371 // definitely do not go out of bounds here
372 if((lba+n) > filecache_maxcdsectors) n = filecache_maxcdsectors - lba;
373 // gdrom reads are non-blocking
374 gdrom_readsectors(dest, (-real_pakfd) + lba, n);
375 }
376 else
377 #endif
378 {
379 #ifdef PS2
380 sceLseek(real_pakfd, pakblock_read * filecache_blocksize, SCE_SEEK_SET);
381 busy = 1; while(busy) sceIoctl(real_pakfd, SCE_FS_EXECUTING, &busy);
382 sceRead(real_pakfd, filecache + (cacheblock_read * filecache_blocksize), filecache_blocksize);
383 #else
384 int disCcWarns;
385 lseek(real_pakfd, pakblock_read * filecache_blocksize, SEEK_SET);
386 disCcWarns = read(real_pakfd, (char*) filecache + (cacheblock_read * filecache_blocksize), filecache_blocksize);
387 #endif
388 }
389 }
390 }
391
392 /////////////////////////////////////////////////////////////////////////////
393 //
394 // attempt to read a block
395 // returns the number of bytes read or 0 on error
396 //
filecache_readpakblock(unsigned char * dest,int pakblock,int startofs,int bytes,int blocking)397 int filecache_readpakblock(unsigned char *dest, int pakblock, int startofs, int bytes, int blocking)
398 {
399 int cacheblock;
400 if(pakblock < 0 || pakblock >= total_pakblocks) return 0;
401 if(bytes < 0) return 0;
402 if(startofs < 0) return 0;
403 if(startofs >= filecache_blocksize) return 0;
404 if((startofs+bytes) > filecache_blocksize) bytes = filecache_blocksize - startofs;
405
406 for(;;)
407 {
408 // see if we can copy from the cache
409 cacheblock = where_is_this_pakblock_cached[pakblock];
410 if(cacheblock < filecache_blocks)
411 {
412 cacheblock_mark_used(cacheblock);
413 memcpy(dest, filecache + (cacheblock * filecache_blocksize) + startofs, bytes);
414 return bytes;
415 }
416
417 // it didn't work
418 // if we're nonblocking, return failure
419 if(!blocking) return 0;
420
421 // otherwise, demand a block
422 request_read_pakblock = pakblock;
423
424 filecache_process();
425 }
426 return bytes;
427 }
428
429 /////////////////////////////////////////////////////////////////////////////
430 //
431 // set up where the vfd pointers are
432 //
filecache_setvfd(int vfd,int start,int block,int readahead)433 void filecache_setvfd(int vfd, int start, int block, int readahead)
434 {
435 if(vfd < 0 || vfd >= max_vfds) return;
436 vfd_startptr_pakblock[vfd] = start;
437 vfd_readptr_pakblock[vfd] = block;
438 vfd_desired_readahead_blocks[vfd] = readahead;
439 }
440
441 /////////////////////////////////////////////////////////////////////////////
442 //
443 // Release All Allocations
444 //
filecache_term()445 void filecache_term()
446 {
447 filecache_blocksize = 32768;
448 filecache_blocks = 96;
449 max_vfds = 8;
450 default_minimum_run_bytes = 131072;
451 real_pakfd = 0;
452 total_pakblocks = 0;
453 cacheblock_lastused = 0;
454 cacheblock_mru_counter = 0;
455 request_read_pakblock = -1;
456 filecache_maxcdsectors = 0;
457 last_cacheblock_read = -1;
458 last_pakblock_read = -1;
459 filecache_ready = 0;
460 pakblock_run_ptr = 0;
461 pakblock_run_len = 0;
462 pakblock_run_min = 1;
463 if(vfd_blocks_available != NULL)
464 {
465 free(vfd_blocks_available);
466 vfd_blocks_available = NULL;
467 }
468 if(cacheblock_mru != NULL)
469 {
470 free(cacheblock_mru);
471 cacheblock_mru = NULL;
472 }
473 if(vfd_desired_readahead_blocks != NULL)
474 {
475 free(vfd_desired_readahead_blocks);
476 vfd_desired_readahead_blocks = NULL;
477 }
478 if(vfd_startptr_pakblock != NULL)
479 {
480 free(vfd_startptr_pakblock);
481 vfd_startptr_pakblock = NULL;
482 }
483 if(vfd_readptr_pakblock != NULL)
484 {
485 free(vfd_readptr_pakblock);
486 vfd_readptr_pakblock = NULL;
487 }
488 if(where_is_this_pakblock_cached != NULL)
489 {
490 free(where_is_this_pakblock_cached);
491 where_is_this_pakblock_cached = NULL;
492 }
493 if(filecache_pakmap != NULL)
494 {
495 free(filecache_pakmap);
496 filecache_pakmap = NULL;
497 }
498 #ifdef PSP
499 // Release 4 MBytes of reserved space by PSP.
500 if(sceKernelVolatileMemUnlock(0))
501 {
502 printf("Error allocation filecache!\n");
503 exit(0);
504 }
505 #elif DC
506 filecache_head = NULL;
507 #else
508 if(filecache_head)
509 {
510 free(filecache_head);
511 filecache_head = NULL;
512 }
513 #endif
514 }
515
516 /////////////////////////////////////////////////////////////////////////////
517 //
518 // BLOCKS MUST BE 255 OR LESS
519 //
filecache_init(int realfd,int pakcdsectors,int blocksize,unsigned char blocks,int vfds)520 void filecache_init(int realfd, int pakcdsectors, int blocksize, unsigned char blocks, int vfds)
521 {
522 int i;
523
524 #ifdef PSP
525 int size;
526 #endif
527
528 real_pakfd = realfd;
529 total_pakblocks = ((pakcdsectors*2048)+(blocksize-1))/blocksize;
530 filecache_blocksize = blocksize;
531 filecache_blocks = blocks;
532 max_vfds = vfds;
533 filecache_maxcdsectors = pakcdsectors;
534
535 // allocate everything
536 #ifdef PSP
537 // This will give us the extra 4 MBytes of reserved space by PSP.
538 if(sceKernelVolatileMemLock(0, (void*)&filecache_head, &size))
539 {
540 printf("Error allocation filecache!\n");
541 exit(0);
542 }
543 #elif DC
544 filecache_head = (void*)(0xA5500000 - 64);
545 #else
546 filecache_head = malloc(filecache_blocksize * filecache_blocks + 64);
547 #endif
548
549 filecache = filecache_head;
550
551 // align the filecache
552 // we can lose this pointer since it'll never be freed anyway and can be reused while running bor
553 // When we exit with sceKernalExitGame All resources are Freed up prior to returning to PSP OS.
554 filecache += 0x40 - (((size_t)filecache) & 0x3F);
555
556 // pakmap: all values should be -1
557 filecache_pakmap = malloc(sizeof(int) * filecache_blocks);
558 for(i = 0; i < filecache_blocks; i++) filecache_pakmap[i] = -1;
559
560 // where_is_this_pakblock_cached: all values should be filecache_blocks
561 where_is_this_pakblock_cached = malloc(total_pakblocks);
562 for(i = 0; i < total_pakblocks; i++) where_is_this_pakblock_cached[i] = filecache_blocks;
563
564 // vfd read pointers: init to -1
565 vfd_readptr_pakblock = malloc(sizeof(int) * max_vfds);
566 for(i = 0; i < max_vfds; i++) vfd_readptr_pakblock[i] = -1;
567
568 // vfd starting pointers: init to -1
569 vfd_startptr_pakblock = malloc(sizeof(int) * max_vfds);
570 for(i = 0; i < max_vfds; i++) vfd_startptr_pakblock[i] = -1;
571
572 // desired readahead: init to 0
573 vfd_desired_readahead_blocks = malloc(sizeof(int) * max_vfds);
574 for(i = 0; i < max_vfds; i++) vfd_desired_readahead_blocks[i] = -1;
575
576 // cache mru: init to 0
577 cacheblock_mru = malloc(sizeof(unsigned) * filecache_blocks);
578 for(i = 0; i < filecache_blocks; i++) cacheblock_mru[i] = 0;
579
580 // blocks available: needs no init
581 vfd_blocks_available = malloc(sizeof(int) * max_vfds);
582
583 filecache_ready = 1;
584 }
585
586 /////////////////////////////////////////////////////////////////////////////
587 //
588 // quick and dirty
589 //
filecache_wait_for_prebuffer(int vfd,int nblocks)590 void filecache_wait_for_prebuffer(int vfd, int nblocks)
591 {
592 if(vfd_readptr_pakblock[vfd] < 0) return;
593 if((vfd_readptr_pakblock[vfd]+nblocks) > total_pakblocks)
594 {
595 nblocks = total_pakblocks - vfd_readptr_pakblock[vfd];
596 }
597 while(get_vfd_blocks_available(vfd) < nblocks) filecache_process();
598 }
599
600 /////////////////////////////////////////////////////////////////////////////
601