/** * @file registry.c * @brief Implements the registry subsystem. */ #include #include #include #include #include #include #include #include #include #include #include SDL_PropertiesID AKGL_REGISTRY_ACTOR = 0; SDL_PropertiesID AKGL_REGISTRY_ACTOR_STATE_STRINGS = 0; SDL_PropertiesID AKGL_REGISTRY_SPRITE = 0; SDL_PropertiesID AKGL_REGISTRY_SPRITESHEET = 0; SDL_PropertiesID AKGL_REGISTRY_CHARACTER = 0; SDL_PropertiesID AKGL_REGISTRY_MUSIC = 0; SDL_PropertiesID AKGL_REGISTRY_FONT = 0; SDL_PropertiesID AKGL_REGISTRY_PROPERTIES = 0; akerr_ErrorContext *akgl_registry_init() { PREPARE_ERROR(errctx); ATTEMPT { CATCH(errctx, akgl_registry_init_spritesheet()); CATCH(errctx, akgl_registry_init_sprite()); CATCH(errctx, akgl_registry_init_character()); CATCH(errctx, akgl_registry_init_actor()); CATCH(errctx, akgl_registry_init_actor_state_strings()); CATCH(errctx, akgl_registry_init_font()); CATCH(errctx, akgl_registry_init_music()); } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_registry_init_actor() { PREPARE_ERROR(errctx); if ( AKGL_REGISTRY_ACTOR != 0 ) { SDL_DestroyProperties(AKGL_REGISTRY_ACTOR); } AKGL_REGISTRY_ACTOR = SDL_CreateProperties(); FAIL_ZERO_RETURN(errctx, AKGL_REGISTRY_ACTOR, AKERR_NULLPOINTER, "Error initializing actor registry"); SUCCEED_RETURN(errctx); } akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_font() { PREPARE_ERROR(errctx); AKGL_REGISTRY_FONT = SDL_CreateProperties(); FAIL_ZERO_RETURN(errctx, AKGL_REGISTRY_FONT, AKERR_NULLPOINTER, "Error initializing font registry"); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_registry_init_music() { PREPARE_ERROR(errctx); AKGL_REGISTRY_MUSIC = SDL_CreateProperties(); FAIL_ZERO_RETURN(errctx, AKGL_REGISTRY_MUSIC, AKERR_NULLPOINTER, "Error initializing music registry"); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_registry_init_properties() { PREPARE_ERROR(errctx); AKGL_REGISTRY_PROPERTIES = SDL_CreateProperties(); FAIL_ZERO_RETURN(errctx, AKGL_REGISTRY_PROPERTIES, AKERR_NULLPOINTER, "Error initializing properties registry"); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_registry_init_actor_state_strings() { int i = 0; int flag = 0; PREPARE_ERROR(errctx); AKGL_REGISTRY_ACTOR_STATE_STRINGS = SDL_CreateProperties(); FAIL_ZERO_RETURN(errctx, AKGL_REGISTRY_ACTOR_STATE_STRINGS, AKERR_NULLPOINTER, "Error initializing actor state strings registry"); for ( i = 0 ; i < AKGL_ACTOR_MAX_STATES; i++ ) { flag = (1 << i); SDL_SetNumberProperty( AKGL_REGISTRY_ACTOR_STATE_STRINGS, AKGL_ACTOR_STATE_STRING_NAMES[i], flag); } SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_registry_init_sprite() { PREPARE_ERROR(errctx); AKGL_REGISTRY_SPRITE = SDL_CreateProperties(); FAIL_ZERO_RETURN(errctx, AKGL_REGISTRY_SPRITE, AKERR_NULLPOINTER, "Error initializing sprite registry"); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_registry_init_spritesheet() { PREPARE_ERROR(errctx); AKGL_REGISTRY_SPRITESHEET = SDL_CreateProperties(); FAIL_ZERO_RETURN(errctx, AKGL_REGISTRY_SPRITESHEET, AKERR_NULLPOINTER, "Error initializing spritesheet registry"); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_registry_init_character() { PREPARE_ERROR(errctx); AKGL_REGISTRY_CHARACTER = SDL_CreateProperties(); FAIL_ZERO_RETURN(errctx, AKGL_REGISTRY_CHARACTER, AKERR_NULLPOINTER, "Error initializing character registry"); SUCCEED_RETURN(errctx); } akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_load_properties(char *fname) { json_t *json = NULL; json_t *props = NULL; const char *pkey = NULL; json_t *pvalue = NULL; json_error_t error; akgl_String *tmpstr; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, fname, AKERR_NULLPOINTER, "null filename"); // One ATTEMPT around the whole function, rather than one for the parse and // a bare loop after it. `props` is a borrowed reference into `json` and is // read by the loop, so the document cannot be released until the loop is // done -- and with the loop outside the block, every path out of it leaked // the document. The per-property ATTEMPT stays nested inside the loop, // which is what AGENTS.md requires: a CATCH written directly in a loop // breaks the loop rather than leaving the function. ATTEMPT { SDL_Log("Loading from %s", fname); json = json_load_file(fname, 0, &error); FAIL_ZERO_BREAK( errctx, json, AKERR_NULLPOINTER, "Error while loading properties from %s on line %d: %s-", fname, error.line, error.text); CATCH(errctx, akgl_get_json_object_value(json, "properties", &props)); json_object_foreach(props, pkey, pvalue) { ATTEMPT { CATCH(errctx, akgl_heap_next_string(&tmpstr)); CATCH(errctx, akgl_get_json_string_value(props, (char *)pkey, &tmpstr)); SDL_SetStringProperty(AKGL_REGISTRY_PROPERTIES, pkey, tmpstr->data); SDL_Log("Set property %s = %s", pkey, tmpstr->data); CATCH(errctx, akgl_heap_release_string(tmpstr)); } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); } } CLEANUP { // Every value was copied into the property store on the way past, so // nothing outlives the document. if ( json != NULL ) { json_decref(json); json = NULL; } } PROCESS(errctx) { } FINISH(errctx, true); SDL_Log("Properties loaded"); SUCCEED_RETURN(errctx); } akerr_ErrorContext AKERR_NOIGNORE *akgl_set_property(char *name, char *src) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, name, AKERR_NULLPOINTER, "NULL char *"); FAIL_ZERO_RETURN(e, src, AKERR_NULLPOINTER, "NULL char *"); SDL_SetStringProperty(AKGL_REGISTRY_PROPERTIES, name, src); SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *akgl_get_property(char *name, akgl_String **dest, char *def) { const char *value = NULL; size_t valuelen = 0; PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, name, AKERR_NULLPOINTER, "NULL char *"); FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "NULL akgl_String *"); ATTEMPT { if ( *dest == NULL ) { CATCH(e, akgl_heap_next_string(dest)); } // Copy the value's own length, not the destination's capacity. What // SDL hands back is its strdup of the value -- four bytes for "0.0" -- // so copying a fixed AKGL_MAX_STRING_LENGTH read up to 4 KiB past the // end of somebody else's allocation on every call. It returned garbage // past the terminator, and it would have faulted outright on a value // that happened to sit at the end of a page. value = SDL_GetStringProperty(AKGL_REGISTRY_PROPERTIES, (const char *)name, (const char *)def); // An unset property with a NULL default still reports AKERR_NULLPOINTER, // which is what the header has always promised. It used to arrive from // inside aksl_memcpy; now it is raised here, before anything is measured. FAIL_ZERO_BREAK( e, value, AKERR_NULLPOINTER, "Property %s is not set and no default was given", name); valuelen = strlen(value); FAIL_NONZERO_BREAK( e, (valuelen >= AKGL_MAX_STRING_LENGTH), AKERR_OUTOFBOUNDS, "Property %s is %zu bytes, which does not fit an akgl_String (%d)", name, valuelen, AKGL_MAX_STRING_LENGTH); CATCH(e, aksl_memcpy((*dest)->data, (void *)value, (valuelen + 1))); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); }