diff --git a/docs/changelog.txt b/docs/changelog.txt index 927fe8bd2..a8a5fa41b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -252,6 +252,7 @@ DEVELOPERS storage, like the NDS. + Board input strings, charset paths, and palette paths are now heap allocated on-demand to save RAM for low-memory systems. ++ Status counters are now saved as a nested properties file. + Refactored the 3DS renderer to use templates. (asie) + intake2() now supports custom handling of intake events, i.e. it can now be used without providing a fixed size buffer. diff --git a/docs/fileform.html b/docs/fileform.html index 28762819e..465527f9e 100644 --- a/docs/fileform.html +++ b/docs/fileform.html @@ -101,7 +101,7 @@ td { - padding: 1px 8px; + padding: 1px 6px; border: 4px solid transparent; } @@ -359,6 +359,9 @@

Contents

  • Files List
  • Properties Format
  • World Properties
  • +
      +
    1. Status Counter Properties
    2. +
  • Custom SFX Table
  • Character Sets
  • Palettes, Palette Indices, and Palette Intensities
  • @@ -2398,7 +2401,8 @@

    Properties Format

    The file ends immediately when a property ID of 0x0000 is encountered indicating the end of the file. This value is the same for all different properties files. Unrecognized properties IDs are usually - skipped unless noted otherwise. + skipped unless noted otherwise. The EOF ID should always be present at + the end of the file and there should be no data after the EOF ID.

    It is possible to nest properties files to create a file with a more @@ -2444,7 +2448,7 @@

    World Properties

    | `0x0012` | ID Chars bullet colors | array(b * 3) | | `0x0013` | ID Chars block 3 (damage) | array(b * 128) | -| `0x0018` | Status counters | array(s15 with \0 * 6) | +| `0x0018` | Status counters | Properties (2.93+)
    array(s15 with \0 * 6) | | `0x0020` | Edge color | int(b) | `0x0021` | First board # | int(b) @@ -2514,6 +2518,19 @@

    World Properties

    name if it is not. +

    Status Counter Properties

    + As of 2.93, the status counters are a nested properties file. Status + counters not present in the properties file are unused/blank. +
    +| ID | Property | Data Type | Notes | +|----------|--------------------------------------|-------------------------|-------| +| `0x0001` | Set current status counter ID | int(b) | Counters >=6 are ignored +| `0x0002` | Status counter name | string | Max. length 14 +
    + Prior to 2.93, the status counters were a fixed size array of 6 15-byte + ASCIIZ strings (total size 90 bytes). In 2.93+ worlds, if the status + counters are 90 bytes long and are not a valid properties file, + they will be loaded as the old format.
    diff --git a/src/utils/downver.c b/src/utils/downver.c index 3196d1132..4cc4c4785 100644 --- a/src/utils/downver.c +++ b/src/utils/downver.c @@ -93,8 +93,6 @@ struct downver_state /* 2.93 conversion vars */ int screen_mode; - uint8_t *smzx_colors; - uint8_t *smzx_intensities; }; static inline void save_prop_p(int ident, struct memfile *prop, @@ -171,6 +169,40 @@ static enum zip_error zip_duplicate_file(struct downver_state *dv, return result; } +static void convert_293_to_292_status_counters(struct downver_state *dv, + struct memfile *dest, struct memfile *src) +{ + char counters[NUM_STATUS_COUNTERS * COUNTER_NAME_SIZE]; + struct memfile prop; + int ident; + int len; + int num = 0; + + memset(counters, 0, sizeof(counters)); + + while(next_prop(&prop, &ident, &len, src)) + { + switch(ident) + { + case STATCTRPROP_SET_ID: + num = load_prop_int(&prop); + break; + + case STATCTRPROP_NAME: + if(num < NUM_STATUS_COUNTERS) + { + char *pos = counters + num * COUNTER_NAME_SIZE; + len = MIN(len, COUNTER_NAME_SIZE - 1); + len = mfread(pos, 1, len, &prop); + pos[len] = '\0'; + } + break; + } + } + save_prop_a(WPROP_STATUS_COUNTERS, counters, + COUNTER_NAME_SIZE, NUM_STATUS_COUNTERS, dest); +} + static void convert_293_to_292_world_info(struct downver_state *dv, struct memfile *dest, struct memfile *src) { @@ -193,6 +225,10 @@ static void convert_293_to_292_world_info(struct downver_state *dv, save_prop_s_to_asciiz(ident, BOARD_NAME_SIZE, &prop, dest); break; + case WPROP_STATUS_COUNTERS: + convert_293_to_292_status_counters(dv, dest, &prop); + break; + case WPROP_SMZX_MODE: /* Load for palette conversion. */ dv->screen_mode = load_prop_int(&prop); diff --git a/src/world.c b/src/world.c index 53121a24c..ec6a8fb5b 100644 --- a/src/world.c +++ b/src/world.c @@ -298,12 +298,30 @@ static inline int save_world_info(struct world *mzx_world, save_prop_a(WPROP_ID_DMG, id_dmg, ID_DMG_SIZE, 1, mf); // Status counters - save_prop_v(WPROP_STATUS_COUNTERS, COUNTER_NAME_SIZE * NUM_STATUS_COUNTERS, - prop, mf); + if(file_version >= V293) + { + char counters[STATCTR_PROP_SIZE]; + mfopen_wr(counters, sizeof(counters), prop); - for(i = 0; i < NUM_STATUS_COUNTERS; i++) + for(i = 0; i < NUM_STATUS_COUNTERS; i++) + { + char *ctr = mzx_world->status_counters_shown[i]; + if(ctr[0]) + { + save_prop_c(STATCTRPROP_SET_ID, i, prop); + save_prop_s(STATCTRPROP_NAME, ctr, prop); + } + } + save_prop_eof(prop); + save_prop_a(WPROP_STATUS_COUNTERS, counters, mftell(prop), 1, mf); + } + else { - mfwrite(mzx_world->status_counters_shown[i], COUNTER_NAME_SIZE, 1, prop); + save_prop_v(WPROP_STATUS_COUNTERS, COUNTER_NAME_SIZE * NUM_STATUS_COUNTERS, + prop, mf); + + for(i = 0; i < NUM_STATUS_COUNTERS; i++) + mfwrite(mzx_world->status_counters_shown[i], COUNTER_NAME_SIZE, 1, prop); } // Global properties @@ -580,6 +598,58 @@ static inline enum val_result validate_world_info(struct world *mzx_world, return VAL_INVALID; } +static inline void load_status_counter_info(struct world *mzx_world, + int *file_version, struct memfile *mf) +{ + struct memfile prop; + int ident; + int len; + size_t num = 0; + boolean load_properties = false; + int i; + + if(*file_version >= V293) + { + // Allow old format status counters in 2.93 worlds for convenience. + // The old format is 90 bytes long and should fail the properties file check. + if(mf->end - mf->start != NUM_STATUS_COUNTERS * COUNTER_NAME_SIZE || + check_properties_file(mf, STATCTRPROP_NAME)) + { + load_properties = true; + } + } + + if(load_properties) + { + while(next_prop(&prop, &ident, &len, mf)) + { + switch(ident) + { + case STATCTRPROP_SET_ID: + num = load_prop_int(&prop); + break; + + case STATCTRPROP_NAME: + if(num < ARRAY_SIZE(mzx_world->status_counters_shown)) + { + len = MIN((size_t)len, sizeof(mzx_world->status_counters_shown[num]) - 1); + len = mfread(mzx_world->status_counters_shown[num], 1, len, &prop); + mzx_world->status_counters_shown[num][len] = '\0'; + } + break; + } + } + } + else + { + for(i = 0; i < NUM_STATUS_COUNTERS; i++) + { + mfread(mzx_world->status_counters_shown[i], COUNTER_NAME_SIZE, 1, mf); + mzx_world->status_counters_shown[i][COUNTER_NAME_SIZE - 1] = '\0'; + } + } +} + #define if_savegame if(!savegame) { break; } #define if_savegame_or_291 if(!savegame && *file_version < V291) { break; } @@ -694,11 +764,7 @@ static inline void load_world_info(struct world *mzx_world, // Status counters case WPROP_STATUS_COUNTERS: - for(i = 0; i < NUM_STATUS_COUNTERS; i++) - { - mfread(mzx_world->status_counters_shown[i], COUNTER_NAME_SIZE, 1, prop); - mzx_world->status_counters_shown[i][COUNTER_NAME_SIZE - 1] = '\0'; - } + load_status_counter_info(mzx_world, file_version, prop); break; // Global properties @@ -3468,6 +3534,8 @@ void clear_world(struct world *mzx_world) int num_boards = mzx_world->num_boards; struct board **board_list = mzx_world->board_list; + memset(mzx_world->status_counters_shown, 0, NUM_STATUS_COUNTERS * COUNTER_NAME_SIZE); + for(i = 0; i < num_boards; i++) { if(mzx_world->current_board_id != i) diff --git a/src/world_format.h b/src/world_format.h index 618e1ef13..50b5fddee 100644 --- a/src/world_format.h +++ b/src/world_format.h @@ -77,12 +77,26 @@ enum world_file_id #define PROP_HEADER_SIZE 6 #define PROP_EOF_SIZE 2 -#define STATS_SIZE NUM_STATUS_COUNTERS * COUNTER_NAME_SIZE +#define COUNT_STATCTR_PROPS (NUM_STATUS_COUNTERS * (2)) +#define BOUND_STATCTR_PROPS (NUM_STATUS_COUNTERS * (1 + COUNTER_NAME_SIZE)) + +#define STATCTR_PROP_SIZE \ +( \ + BOUND_STATCTR_PROPS + \ + COUNT_STATCTR_PROPS * PROP_HEADER_SIZE + \ + PROP_EOF_SIZE \ +) + +enum status_counters_prop +{ + STATCTRPROP_SET_ID = 0x0001, // 1 + STATCTRPROP_NAME = 0x0002, // COUNTER_NAME_SIZE +}; // IF YOU ADD ANYTHING, MAKE SURE THIS GETS UPDATED! -#define COUNT_WORLD_PROPS ( 1 + 3 + 4 + 16 + 4 + 1) -#define BOUND_WORLD_PROPS (BOARD_NAME_SIZE + 5 + 455 + 24 + 9 + STATS_SIZE) +#define COUNT_WORLD_PROPS ( 1 + 3 + 4 + 16 + 4 + 1) +#define BOUND_WORLD_PROPS (BOARD_NAME_SIZE + 5 + 455 + 24 + 9 + STATCTR_PROP_SIZE) #define COUNT_SAVE_PROPS ( 2 + 32 + 3 + 1) #define BOUND_SAVE_PROPS ( 2 + 120 + 3*MAX_PATH + NUM_KEYS) @@ -919,6 +933,40 @@ static inline boolean next_prop(struct memfile *prop, int *ident, int *length, return true; } +/** + * Returns true if the properties file in `mf` is valid. + * This can be used to distinguish whether or not an ambiguous file or field + * actually contains properties. + */ +static inline boolean check_properties_file(struct memfile *mf, + int maximum_ident) +{ + int ident; + unsigned length; + while(mf->end - mf->current >= 6) + { + ident = mfgetw(mf); + length = mfgetud(mf); + if(ident == 0 || ident > maximum_ident) + goto err; + + if((unsigned)(mf->end - mf->current) < length) + goto err; + + mf->current += length; + } + + if((mf->end - mf->current == 2) && mfgetw(mf) == 0x0000) // EOF + { + mf->current = mf->start; + return true; + } + +err: + mf->current = mf->start; + return false; +} + __M_END_DECLS #endif // __WORLD_FORMAT_H