/* * memory.c * * Code and data caching routines * */ #include "ztypes.h" /* A cache entry */ typedef struct cache_entry { struct cache_entry *flink; int page_number; zbyte_t data[PAGE_SIZE]; } cache_entry_t; /* Cache chain anchor */ static cache_entry_t *cache = NULL; /* Pseudo translation buffer, one entry each for code and data pages */ static unsigned int current_code_page = 0; static cache_entry_t *current_code_cachep = NULL; static unsigned int current_data_page = 0; static cache_entry_t *current_data_cachep = NULL; #ifdef __STDC__ static unsigned int calc_data_pages (void); static cache_entry_t *update_cache (int); #else static unsigned int calc_data_pages (); static cache_entry_t *update_cache (); #endif /* * load_cache * * Initialise the cache and any other dynamic memory objects. The memory * required can be split into two areas. Firstly, three buffers are required for * input, output and status line. Secondly, two data areas are required for * writeable data and read only data. The writeable data is the first chunk of * the file and is put into non-paged cache. The read only data is the remainder * of the file which can be paged into the cache as required. Writeable data has * to be memory resident because it cannot be written out to a backing store. * */ #ifdef __STDC__ void load_cache (void) #else void load_cache () #endif { unsigned long file_size; unsigned int i, file_pages, data_pages; cache_entry_t *cachep; /* Allocate output and status line buffers */ line = (char *) malloc (screen_cols + 1); if (line == NULL) fatal ("Insufficient memory to play game"); status_line = (char *) malloc (screen_cols + 1); if (status_line == NULL) fatal ("Insufficient memory to play game"); /* Must have at least one cache page for memory calculation */ cachep = (cache_entry_t *) malloc (sizeof (cache_entry_t)); if (cachep == NULL) fatal ("Insufficient memory to play game"); cachep->flink = cache; cachep->page_number = 0; cache = cachep; /* Calculate dynamic cache pages required */ if (h_config & CONFIG_MAX_DATA) data_pages = calc_data_pages (); else data_pages = (h_data_size + PAGE_MASK) >> PAGE_SHIFT; data_size = data_pages * PAGE_SIZE; file_size = (unsigned long) h_file_size * story_scaler; file_pages = (unsigned int) ((file_size + PAGE_MASK) >> PAGE_SHIFT); /* Allocate static data area and initialise it */ datap = (zbyte_t *) malloc (data_size); if (datap == NULL) fatal ("Insufficient memory to play game"); for (i = 0; i < data_pages; i++) read_page (i, &datap[i * PAGE_SIZE]); /* Allocate memory for undo */ undo_datap = (zbyte_t *) malloc (data_size); /* Allocate cache pages and initialise them */ for (i = data_pages; cachep != NULL && i < file_pages; i++) { cachep = (cache_entry_t *) malloc (sizeof (cache_entry_t)); if (cachep != NULL) { cachep->flink = cache; cachep->page_number = i; read_page (cachep->page_number, cachep->data); cache = cachep; } } }/* load_cache */ /* * unload_cache * * Deallocate cache and other memory objects. * */ #ifdef __STDC__ void unload_cache (void) #else void unload_cache () #endif { cache_entry_t *cachep, *nextp; /* Make sure all output has been flushed */ new_line (); /* Free output buffer, status line and data memory */ free (line); free (status_line); free (datap); free (undo_datap); /* Free cache memory */ for (cachep = cache; cachep->flink != NULL; cachep = nextp) { nextp = cachep->flink; free (cachep); } }/* unload_cache */ /* * read_code_word * * Read a word from the instruction stream. * */ #ifdef __STDC__ zword_t read_code_word (void) #else zword_t read_code_word () #endif { zword_t w; w = (zword_t) read_code_byte () << 8; w |= (zword_t) read_code_byte (); return (w); }/* read_code_word */ /* * read_code_byte * * Read a byte from the instruction stream. * */ #ifdef __STDC__ zbyte_t read_code_byte (void) #else zbyte_t read_code_byte () #endif { unsigned int page_number, page_offset; /* Calculate page and offset values */ page_number = (unsigned int) (pc >> PAGE_SHIFT); page_offset = (unsigned int) pc & PAGE_MASK; /* Load page into translation buffer */ if (page_number != current_code_page) { current_code_cachep = update_cache (page_number); current_code_page = page_number; } /* Update the PC */ pc++; /* Return byte from page offset */ return (current_code_cachep->data[page_offset]); }/* read_code_byte */ /* * read_data_word * * Read a word from the data area. * */ #ifdef __STDC__ zword_t read_data_word (unsigned long *addr) #else zword_t read_data_word (addr) unsigned long *addr; #endif { zword_t w; w = (zword_t) read_data_byte (addr) << 8; w |= (zword_t) read_data_byte (addr); return (w); }/* read_data_word */ /* * read_data_byte * * Read a byte from the data area. * */ #ifdef __STDC__ zbyte_t read_data_byte (unsigned long *addr) #else zbyte_t read_data_byte (addr) unsigned long *addr; #endif { unsigned int page_number, page_offset; zbyte_t value; /* Check if byte is in non-paged cache */ if (*addr < (unsigned long) data_size) value = datap[*addr]; else { /* Calculate page and offset values */ page_number = (int) (*addr >> PAGE_SHIFT); page_offset = (int) *addr & PAGE_MASK; /* Load page into translation buffer */ if (page_number != current_data_page) { current_data_cachep = update_cache (page_number); current_data_page = page_number; } /* Fetch byte from page offset */ value = current_data_cachep->data[page_offset]; } /* Update the address */ (*addr)++; return (value); }/* read_data_byte */ /* * calc_data_pages * * Compute the best size for the data area cache. Some games have the data size * header parameter set too low. This causes a write outside of data area on * some games. To alleviate this problem the data area size is set to the * maximum of the restart size, the data size and the end of the dictionary. An * attempt is made to put the dictionary in the data area to stop paging during * a dictionary lookup. Some games have the dictionary end very close to the * 64K limit which may cause problems for machines that allocate memory in * 64K chunks. * */ #ifdef __STDC__ static unsigned int calc_data_pages (void) #else static unsigned int calc_data_pages () #endif { unsigned long offset, data_end, dictionary_end; int separator_count, word_size, word_count; unsigned int data_pages; /* Calculate end of data area, use restart size if data size is too low */ if (h_data_size > h_restart_size) data_end = h_data_size; else data_end = h_restart_size; /* Calculate end of dictionary table */ offset = h_words_offset; separator_count = read_data_byte (&offset); offset += separator_count; word_size = read_data_byte (&offset); word_count = read_data_word (&offset); dictionary_end = offset + (word_size * word_count); /* If data end is too low then use end of dictionary instead */ if (dictionary_end > data_end) data_pages = (unsigned int) ((dictionary_end + PAGE_MASK) >> PAGE_SHIFT); else data_pages = (unsigned int) ((data_end + PAGE_MASK) >> PAGE_SHIFT); return (data_pages); }/* calc_data_pages */ /* * update_cache * * Called on a code or data page cache miss to find the page in the cache or * read the page in from disk. The chain is kept as a simple LRU chain. If a * page cannot be found then the page on the end of the chain is reused. If the * page is found, or reused, then it is moved to the front of the chain. * */ #ifdef __STDC__ static cache_entry_t *update_cache (int page_number) #else static cache_entry_t *update_cache (page_number) int page_number; #endif { cache_entry_t *cachep, *lastp; /* Search the cache chain for the page */ for (lastp = cache, cachep = cache; cachep->flink != NULL && cachep->page_number && cachep->page_number != page_number; lastp = cachep, cachep = cachep->flink) ; /* If no page in chain then read it from disk */ if (cachep->page_number != page_number) { /* Reusing last cache page, so invalidate cache if page was in use */ if (cachep->flink == NULL && cachep->page_number) { if (current_code_page == (unsigned int) cachep->page_number) current_code_page = 0; if (current_data_page == (unsigned int) cachep->page_number) current_data_page = 0; } /* Load the new page number and the page contents from disk */ cachep->page_number = page_number; read_page (page_number, cachep->data); } /* If page is not at front of cache chain then move it there */ if (lastp != cache) { lastp->flink = cachep->flink; cachep->flink = cache; cache = cachep; } return (cachep); }/* update_cache */