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
+
+ - Status Counter Properties
+
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