Пример #1
0
TOOLKIT_DEINIT_FUNC_FINISH

/**
 * Joins two path components, eg, '/usr' and 'bin' -> '/usr/bin'.
 * @param path
 * First path component.
 * @param path2
 * Second path component.
 * @return
 * The joined path; should be freed when no longer needed.
 */
char *path_join(const char *path, const char *path2)
{
    StringBuffer *sb;
    size_t len;
    char *cp;

    TOOLKIT_PROTECT();

    sb = stringbuffer_new();
    stringbuffer_append_string(sb, path);

    len = strlen(path);

    if (len && path[len - 1] != '/') {
        stringbuffer_append_string(sb, "/");
    }

    stringbuffer_append_string(sb, path2);
    cp = stringbuffer_finish(sb);

    return cp;
}
Пример #2
0
/**
 * Normalize a path, eg, foo//bar, foo/foo2/../bar, foo/./bar all become
 * foo/bar.
 *
 * If the path begins with either a forward slash or a dot *and* a forward
 * slash, they will be preserved.
 * @param path
 * Path to normalize.
 * @return
 * The normalized path; never NULL. Must be freed.
 */
char *path_normalize(const char *path)
{
    StringBuffer *sb;
    size_t pos, startsbpos;
    char component[MAX_BUF];
    ssize_t last_slash;

    TOOLKIT_PROTECT();

    if (string_isempty(path)) {
        return estrdup(".");
    }

    sb = stringbuffer_new();
    pos = 0;

    if (string_startswith(path, "/")) {
        stringbuffer_append_string(sb, "/");
    } else if (string_startswith(path, "./")) {
        stringbuffer_append_string(sb, "./");
    }

    startsbpos = stringbuffer_length(sb);

    while (string_get_word(path, &pos, '/', component, sizeof(component), 0)) {
        if (strcmp(component, ".") == 0) {
            continue;
        }

        if (strcmp(component, "..") == 0) {
            if (stringbuffer_length(sb) > startsbpos) {
                last_slash = stringbuffer_rindex(sb, '/');

                if (last_slash == -1) {
                    LOG(BUG, "Should have found a forward slash, but didn't: %s", path);
                    continue;
                }

                stringbuffer_seek(sb, last_slash);
            }
        } else {
            size_t len = stringbuffer_length(sb);
            if (len == 0 || stringbuffer_data(sb)[len - 1] != '/') {
                stringbuffer_append_string(sb, "/");
            }

            stringbuffer_append_string(sb, component);
        }
    }

    if (stringbuffer_length(sb) == 0) {
        stringbuffer_append_string(sb, ".");
    }

    return stringbuffer_finish(sb);
}
Пример #3
0
/**
 * Create a path to the per-server settings directory.
 * @param path
 * Path inside the per-server settings directory.
 * @return
 * New path. Must be freed.
 */
char *file_path_server(const char *path)
{
    StringBuffer *sb;

    HARD_ASSERT(path != NULL);

    sb = file_path_server_internal();
    stringbuffer_append_printf(sb, ".common/%s", path);

    return stringbuffer_finish(sb);
}
Пример #4
0
/**
 * Get absolute path to the specified relative path. This path will typically
 * point to the to client data directory (which is usually located in the user's
 * home/appdata directory), but depending on the specified mode, extra actions
 * may be performed. These ensure that if you're trying to access a file that
 * does not yet exist in the client data directory, it will be read from the
 * client installation directory instead (unless it's being appended to, in
 * which case it will be copied to the client data directory first).
 *
 * Generally, you should almost always use this when you need to construct a
 * path, or use one of the many @ref file_wrapper_functions.
 * @param fname
 * The file path.
 * @param mode
 * File mode.
 * @return
 * The absolute path. Must be freed.
 */
char *file_path(const char *path, const char *mode)
{
    bool is_write, is_append;
    StringBuffer *sb;
    char version[MAX_BUF], client_path[HUGE_BUF], *new_path;

    HARD_ASSERT(path != NULL);
    HARD_ASSERT(mode != NULL);

    SOFT_ASSERT_RC(path[0] != '/', estrdup(path),
            "Path is already absolute: %s", path);

    sb = stringbuffer_new();
    stringbuffer_append_printf(sb, "%s/.atrinik/%s/%s", get_config_dir(),
            package_get_version_partial(VS(version)), path);
    new_path = stringbuffer_sub(sb, 0, 0);

    is_write = is_append = false;

    if (strchr(mode, 'w') != NULL) {
        is_write = true;
    } else if (strchr(mode, '+') != NULL || strchr(mode, 'a') != NULL) {
        is_append = true;
    }

    if (is_write || is_append) {
        if (access(new_path, W_OK) != 0) {
            char *dirname;

            /* Ensure directories exist if we're going to use this path for
             * writing/appending. */
            dirname = path_dirname(new_path);
            mkdir_recurse(dirname);
            efree(dirname);

            if (is_append) {
                get_data_dir_file(VS(client_path), path);
                copy_file(client_path, new_path);
            }
        }
    } else {
        if (access(new_path, R_OK) != 0) {
            get_data_dir_file(VS(client_path), path);
            stringbuffer_seek(sb, 0);
            stringbuffer_append_string(sb, client_path);
        }
    }

    efree(new_path);

    return stringbuffer_finish(sb);
}
Пример #5
0
/**
 * Create a path to the per-player settings directory.
 * @param path
 * Path inside the per-player settings directory.
 * @return
 * New path. Must be freed.
 */
char *file_path_player(const char *path)
{
    StringBuffer *sb;

    HARD_ASSERT(path != NULL);

    sb = file_path_server_internal();

    SOFT_ASSERT_LABEL(*cpl.account != '\0', done, "Account name is empty.");
    SOFT_ASSERT_LABEL(*cpl.name != '\0', done, "Player name is empty.");

    stringbuffer_append_printf(sb, "%s/%s/%s", cpl.account, cpl.name, path);

done:
    return stringbuffer_finish(sb);
}
Пример #6
0
/**
 * Load the entire contents of file 'path' into a StringBuffer instance,
 * then return the created string.
 * @param path
 * File to load contents of.
 * @return
 * The loaded contents. Must be freed.
 */
char *path_file_contents(const char *path)
{
    FILE *fp;
    StringBuffer *sb;
    char buf[MAX_BUF];

    TOOLKIT_PROTECT();

    fp = fopen(path, "rb");

    if (!fp) {
        return NULL;
    }

    sb = stringbuffer_new();

    while (fgets(buf, sizeof(buf), fp)) {
        stringbuffer_append_string(sb, buf);
    }

    fclose(fp);

    return stringbuffer_finish(sb);
}
Пример #7
0
/**
 * Generate a message detailing the properties of 1-6 artifacts drawn
 * sequentially from the artifact list.
 * @param level Level of the book
 * @param buf Buffer to contain the description.
 * @param booksize Length of the book.
 * @return 'buf'. */
static char *artifact_msg(int level, char *buf, size_t booksize)
{
	artifactlist *al;
	artifact *art;
	int chance, i, type, index;
	int book_entries = level > 5 ? RANDOM () % 3 + RANDOM () % 3 + 2 : RANDOM () % level + 1;
	char *final, *ch;
	object *tmp = NULL;
	StringBuffer *desc;

	/* Values greater than 5 create msg buffers that are too big! */
	if (book_entries > 5)
	{
		book_entries = 5;
	}

	/* Let's determine what kind of artifact type randomly.
	 * Right now legal artifacts only come from those listed
	 * in art_name_array. Also, we check to be sure an artifactlist
	 * for that type exists! */
	i = 0;

	do
	{
		index = rndm(1, arraysize(art_name_array)) - 1;
		type = art_name_array[index].type;
		al = find_artifactlist(type);
		i++;
	}
	while (al == NULL && i < 10);

	/* Unable to find a message */
	if (i == 10)
	{
		snprintf(buf, booksize, "None");
		return buf;
	}

	/* There is no reason to start on the artifact list at the begining. Lets
	 * take our starting position randomly... */
	art = al->items;

	for (i = rndm(1, level) + rndm(0, 1); i > 0; i--)
	{
		/* Out of stuff, loop back around */
		if (art == NULL)
		{
			art = al->items;
		}

		art = art->next;
	}

	/* Ok, let's print out the contents */
	snprintf(buf, booksize, "<t t=\"Magical %s\">Herein %s detailed %s...\n", art_name_array[index].name, book_entries > 1 ? "are" : "is", book_entries > 1 ? "some artifacts" : "an artifact");

	/* Artifact msg attributes loop. Let's keep adding entries to the 'book'
	 * as long as we have space up to the allowed max # (book_entires) */
	while (book_entries > 0)
	{
		if (art == NULL)
		{
			art = al->items;
		}

		desc = stringbuffer_new();
		tmp = get_archetype(art->def_at_name);
		give_artifact_abilities(tmp, art);
		SET_FLAG(tmp, FLAG_IDENTIFIED);

		stringbuffer_append_printf(desc, "\n<t t=\"%s %s\">It is ", tmp->name, tmp->title ? tmp->title : "");

		/* Chance of finding. */
		chance = 100 * ((float) art->chance / al->total_chance);

		if (chance >= 20)
		{
			stringbuffer_append_string(desc, "an uncommon");
		}
		else if (chance >= 10)
		{
			stringbuffer_append_string(desc, "an unusual");
		}
		else if (chance >= 5)
		{
			stringbuffer_append_string(desc, "a rare");
		}
		else
		{
			stringbuffer_append_string(desc, "a very rare");
		}

		/* Value of artifact. */
		stringbuffer_append_printf(desc, " item with a value of %s.", cost_string_from_value(tmp->value));

		if ((ch = describe_item(tmp)) && strlen(ch) > 1)
		{
			stringbuffer_append_printf(desc, "\nProperties of this artifact include:\n %s", ch);
		}

		final = stringbuffer_finish(desc);

		/* Add the buf if it will fit. */
		if (book_overflow(buf, final, booksize))
		{
			free(final);
			break;
		}

		snprintf(buf + strlen(buf), booksize - strlen(buf), "%s", final);
		free(final);

		art = art->next;
		book_entries--;
	}
Пример #8
0
/**
 * Analyze /cmd type commands the player has typed in the console or bound to a
 * key.
 * Sort out the "client intern" commands and expand or pre process them for the
 * server.
 * @param cmd
 * Command to check
 * @return
 * 0 to send command to server, 1 to not send it
 */
int client_command_check(const char *cmd)
{
    if (cmd_aliases_handle(cmd)) {
        return 1;
    } else if (strncasecmp(cmd, "/ready_spell", 12) == 0) {
        cmd = strchr(cmd, ' ');

        if (!cmd || *++cmd == '\0') {
            draw_info(COLOR_RED, "Usage: /ready_spell <spell name>");
            return 1;
        } else {
            object *tmp;

            for (tmp = cpl.ob->inv; tmp; tmp = tmp->next) {
                if (tmp->itype == TYPE_SPELL && strncasecmp(tmp->s_name, cmd, strlen(cmd)) == 0) {
                    if (!(tmp->flags & CS_FLAG_APPLIED)) {
                        client_send_apply(tmp);
                    }

                    return 1;
                }
            }
        }

        draw_info(COLOR_RED, "Unknown spell.");
        return 1;
    } else if (strncasecmp(cmd, "/ready_skill", 12) == 0) {
        cmd = strchr(cmd, ' ');

        if (!cmd || *++cmd == '\0') {
            draw_info(COLOR_RED, "Usage: /ready_skill <skill name>");
            return 1;
        } else {
            object *tmp;

            for (tmp = cpl.ob->inv; tmp; tmp = tmp->next) {
                if (tmp->itype == TYPE_SKILL && strncasecmp(tmp->s_name, cmd, strlen(cmd)) == 0) {
                    if (!(tmp->flags & CS_FLAG_APPLIED)) {
                        client_send_apply(tmp);
                    }

                    return 1;
                }
            }
        }

        draw_info(COLOR_RED, "Unknown skill.");
        return 1;
    } else if (!strncmp(cmd, "/help", 5)) {
        cmd += 5;

        if (*cmd == '\0') {
            help_show("main");
        } else {
            help_show(cmd + 1);
        }

        return 1;
    } else if (!strncmp(cmd, "/resetwidgets", 13)) {
        widgets_reset();
        return 1;
    } else if (!strncmp(cmd, "/effect ", 8)) {
        if (!strcmp(cmd + 8, "none")) {
            effect_stop();
            draw_info(COLOR_GREEN, "Stopped effect.");
            return 1;
        }

        if (effect_start(cmd + 8)) {
            draw_info_format(COLOR_GREEN, "Started effect %s.", cmd + 8);
        } else {
            draw_info_format(COLOR_RED, "No such effect %s.", cmd + 8);
        }

        return 1;
    } else if (!strncmp(cmd, "/d_effect ", 10)) {
        effect_debug(cmd + 10);
        return 1;
    } else if (!strncmp(cmd, "/music_pause", 12)) {
        sound_pause_music();
        return 1;
    } else if (!strncmp(cmd, "/music_resume", 13)) {
        sound_resume_music();
        return 1;
    } else if (!strncmp(cmd, "/party joinpassword ", 20)) {
        cmd += 20;

        if (cpl.partyjoin[0] != '\0') {
            char buf[MAX_BUF];

            snprintf(VS(buf), "/party join %s\t%s", cpl.partyjoin, cmd);
            send_command(buf);
        }

        return 1;
    } else if (!strncmp(cmd, "/invfilter ", 11)) {
        inventory_filter_set_names(cmd + 11);
        return 1;
    } else if (!strncasecmp(cmd, "/screenshot", 11)) {
        SDL_Surface *surface_save;

        cmd += 11;

        if (!strncasecmp(cmd, " map", 4)) {
            surface_save = cur_widget[MAP_ID]->surface;
        } else {
            surface_save = ScreenSurface;
        }

        if (!surface_save) {
            draw_info(COLOR_RED, "No surface to save.");
            return 1;
        }

        screenshot_create(surface_save);
        return 1;
    } else if (!strncasecmp(cmd, "/console-load ", 14)) {
        FILE *fp;
        char path[HUGE_BUF], buf[HUGE_BUF * 4], *cp;
        StringBuffer *sb;

        cmd += 14;

        snprintf(path, sizeof(path), "%s/.atrinik/console/%s", get_config_dir(), cmd);

        fp = fopen(path, "r");

        if (!fp) {
            draw_info_format(COLOR_RED, "Could not read %s.", path);
            return 1;
        }

        send_command("/console noinf::");

        while (fgets(buf, sizeof(buf) - 1, fp)) {
            cp = strchr(buf, '\n');

            if (cp) {
                *cp = '\0';
            }

            sb = stringbuffer_new();
            stringbuffer_append_string(sb, "/console noinf::");
            stringbuffer_append_string(sb, buf);
            cp = stringbuffer_finish(sb);
            send_command(cp);
            efree(cp);
        }

        send_command("/console noinf::");

        fclose(fp);

        return 1;
    } else if (strncasecmp(cmd, "/console-obj", 11) == 0) {
        menu_inventory_loadtoconsole(cpl.inventory_focus, NULL, NULL);
        return 1;
    } else if (strncasecmp(cmd, "/patch-obj", 11) == 0) {
        menu_inventory_patch(cpl.inventory_focus, NULL, NULL);
        return 1;
    } else if (string_startswith(cmd, "/cast ") || string_startswith(cmd, "/use_skill ")) {
        object *tmp;
        uint8_t type;

        type = string_startswith(cmd, "/cast ") ? TYPE_SPELL : TYPE_SKILL;
        cmd = strchr(cmd, ' ') + 1;

        if (string_isempty(cmd)) {
            return 1;
        }

        for (tmp = cpl.ob->inv; tmp; tmp = tmp->next) {
            if (tmp->itype == type && strncasecmp(tmp->s_name, cmd, strlen(cmd)) == 0) {
                client_send_fire(5, tmp->tag);
                return 1;
            }
        }

        draw_info_format(COLOR_RED, "Unknown %s.", type == TYPE_SPELL ? "spell" : "skill");
        return 1;
    } else if (strncasecmp(cmd, "/clearcache", 11) == 0) {
        cmd += 12;

        if (string_isempty(cmd)) {
            return 1;
        }

        if (strcasecmp(cmd, "sound") == 0) {
            sound_clear_cache();
            draw_info(COLOR_GREEN, "Sound cache cleared.");
        } else if (strcasecmp(cmd, "textures") == 0) {
            texture_reload();
            draw_info(COLOR_GREEN, "Textures reloaded.");
        }

        return 1;
    } else if (string_startswith(cmd, "/droptag ") ||
            string_startswith(cmd, "/gettag ")) {
        char *cps[3];
        unsigned long int loc, tag, num;

        if (string_split(strchr(cmd, ' ') + 1, cps, arraysize(cps), ' ') !=
                arraysize(cps)) {
            return 1;
        }

        loc = strtoul(cps[0], NULL, 10);
        tag = strtoul(cps[1], NULL, 10);
        num = strtoul(cps[2], NULL, 10);
        client_send_move(loc, tag, num);

        if (string_startswith(cmd, "/gettag ")) {
            sound_play_effect("get.ogg", 100);
        } else {
            sound_play_effect("drop.ogg", 100);
        }

        return 1;
    } else if (string_startswith(cmd, "/talk")) {
        char type[MAX_BUF], npc_name[MAX_BUF];
        size_t pos;
        uint8_t type_num;
        packet_struct *packet;

        pos = 5;

        if (!string_get_word(cmd, &pos, ' ', type, sizeof(type), 0) || string_isempty(cmd + pos)) {
            return 1;
        }

        type_num = atoi(type);

        if (type_num == CMD_TALK_NPC_NAME && (!string_get_word(cmd, &pos, ' ', npc_name, sizeof(npc_name), '"') || string_isempty(cmd + pos))) {
            return 1;
        }

        packet = packet_new(SERVER_CMD_TALK, 64, 64);
        packet_append_uint8(packet, type_num);

        if (type_num == CMD_TALK_NPC || type_num == CMD_TALK_NPC_NAME) {
            if (type_num == CMD_TALK_NPC_NAME) {
                packet_append_string_terminated(packet, npc_name);
            }

            packet_append_string_terminated(packet, cmd + pos);
        } else {
            char tag[MAX_BUF];

            if (!string_get_word(cmd, &pos, ' ', tag, sizeof(tag), 0) || string_isempty(cmd + pos)) {
                packet_free(packet);
                return 1;
            }

            packet_append_uint32(packet, atoi(tag));
            packet_append_string_terminated(packet, cmd + pos);
        }

        socket_send_packet(packet);

        return 1;
    } else if (string_startswith(cmd, "/widget_toggle")) {
        size_t pos;
        char word[MAX_BUF], *cps[2];
        int widget_id;

        pos = 14;

        while (string_get_word(cmd, &pos, ' ', word, sizeof(word), 0)) {
            if (string_split(word, cps, arraysize(cps), ':') < 1) {
                continue;
            }

            widget_id = widget_id_from_name(cps[0]);

            /* Invalid widget ID */
            if (widget_id == -1) {
                continue;
            }

            /* Redraw all or a specific one identified by its UID */
            if (cps[1] == NULL) {
                WIDGET_SHOW_TOGGLE_ALL(widget_id);
            } else {
                widgetdata *widget;

                widget = widget_find(NULL, widget_id, cps[1], NULL);

                if (widget) {
                    WIDGET_SHOW_TOGGLE(widget);
                }
            }
        }

        return 1;
    } else if (string_startswith(cmd, "/widget_focus")) {
        size_t pos;
        char word[MAX_BUF], *cps[2];
        int widget_id;

        pos = 14;

        while (string_get_word(cmd, &pos, ' ', word, sizeof(word), 0)) {
            if (string_split(word, cps, arraysize(cps), ':') < 1) {
                continue;
            }

            widget_id = widget_id_from_name(cps[0]);
            if (widget_id == -1) {
                /* Invalid widget ID */
                continue;
            }

            widget_switch_focus(widget_id, cps[1]);
        }

        return 1;
    } else if (string_startswith(cmd, "/ping")) {
        keepalive_ping_stats();
        return 1;
    } else if (string_startswith(cmd, "/region_map")) {
        region_map_open();
        return 1;
    }

    return 0;
}
Пример #9
0
/**
 * Parse the metaserver certificate information.
 *
 * @param server
 * Metaserver entry.
 * @return
 * True on success, false on failure.
 */
static bool
parse_metaserver_cert (server_struct *server)
{
    HARD_ASSERT(server != NULL);

    if (server->cert == NULL || server->cert_sig == NULL) {
        /* No certificate. */
        return true;
    }

    /* Generate a SHA512 hash of the certificate's contents. */
    unsigned char cert_digest[SHA512_DIGEST_LENGTH];
    if (SHA512((unsigned char *) server->cert,
               strlen(server->cert),
               cert_digest) == NULL) {
        LOG(ERROR, "SHA512() failed: %s",
            ERR_error_string(ERR_get_error(), NULL));
        return false;
    }

    char cert_hash[SHA512_DIGEST_LENGTH * 2 + 1];
    SOFT_ASSERT_RC(string_tohex(VS(cert_digest),
                                VS(cert_hash),
                                false) == sizeof(cert_hash) - 1,
                   false,
                   "string_tohex failed");
    string_tolower(cert_hash);

    /* Verify the signature. */
    if (!curl_verify(CURL_PKEY_TRUST_ULTIMATE,
                     cert_hash,
                     strlen(cert_hash),
                     server->cert_sig,
                     server->cert_sig_len)) {
        LOG(ERROR, "Failed to verify signature");
        return false;
    }

    server_cert_info_t *info = ecalloc(1, sizeof(*info));

    char buf[MAX_BUF];
    size_t pos = 0;
    bool in_info = false;
    while (string_get_word(server->cert, &pos, '\n', VS(buf), 0)) {
        char *cp = buf;
        string_skip_whitespace(cp);
        string_strip_newline(cp);

        if (*cp == '\0') {
            continue;
        }

        if (strcmp(cp, cert_begin_str) == 0) {
            in_info = true;
            continue;
        } else if (!in_info) {
            continue;
        } else if (strcmp(cp, cert_end_str) == 0) {
            break;
        }

        char *cps[2];
        if (string_split(cp, cps, arraysize(cps), ':') != arraysize(cps)) {
            LOG(ERROR, "Parsing error");
            continue;
        }

        string_tolower(cps[0]);
        string_skip_whitespace(cps[1]);
        const char *key = cps[0];
        const char *value = cps[1];
        char **content = NULL;
        if (strcmp(key, "name") == 0) {
            content = &info->name;
        } else if (strcmp(key, "hostname") == 0) {
            content = &info->hostname;
        } else if (strcmp(key, "ipv4 address") == 0) {
            content = &info->ipv4_address;
        } else if (strcmp(key, "ipv6 address") == 0) {
            content = &info->ipv6_address;
        } else if (strcmp(key, "public key") == 0) {
            content = &info->pubkey;
        } else if (strcmp(key, "port") == 0) {
            info->port = atoi(value);
        } else if (strcmp(key, "crypto port") == 0) {
            info->port_crypto = atoi(value);
        } else {
            LOG(DEVEL, "Unrecognized key: %s", key);
            continue;
        }

        if (content != NULL) {
            StringBuffer *sb = stringbuffer_new();

            if (*content != NULL) {
                stringbuffer_append_string(sb, *content);
                stringbuffer_append_char(sb, '\n');
                efree(*content);
            }

            stringbuffer_append_string(sb, value);
            *content = stringbuffer_finish(sb);
        }
    }

    /* Ensure we got the data we need. */
    if (info->name == NULL ||
        info->hostname == NULL ||
        info->pubkey == NULL ||
        info->port_crypto <= 0 ||
        (info->ipv4_address == NULL) != (info->ipv6_address == NULL)) {
        LOG(ERROR,
            "Certificate is missing required data.");
        goto error;
    }

    /* Ensure certificate attributes match the advertised ones. */
    if (strcmp(info->hostname, server->hostname) != 0) {
        LOG(ERROR,
            "Certificate hostname does not match advertised hostname.");
        goto error;
    }

    if (strcmp(info->name, server->name) != 0) {
        LOG(ERROR,
            "Certificate name does not match advertised name.");
        goto error;
    }

    if (info->port != server->port) {
        LOG(ERROR,
            "Certificate port does not match advertised port.");
        goto error;
    }

    if (info->port_crypto != server->port_crypto) {
        LOG(ERROR,
            "Certificate crypto port does not match advertised crypto port.");
        goto error;
    }

    server->cert_info = info;
    return true;

error:
    metaserver_cert_free(info);
    return false;
}
Пример #10
0
/**
 * Player examines some object.
 * @param op Player.
 * @param tmp Object to examine. */
void examine(object *op, object *tmp)
{
	char buf[VERY_BIG_BUF], tmp_buf[64];
	int i;

	if (tmp == NULL || tmp->type == CLOSE_CON)
	{
		return;
	}

	strcpy(buf, "That is ");
	strncat(buf, long_desc(tmp, op), VERY_BIG_BUF - strlen(buf) - 1);
	buf[VERY_BIG_BUF - 1] = '\0';

	/* Only add this for usable items, not for objects like walls or
	 * floors for example. */
	if (!QUERY_FLAG(tmp, FLAG_IDENTIFIED) && need_identify(tmp))
	{
		strncat(buf, " (unidentified)", VERY_BIG_BUF - strlen(buf) - 1);
	}

	buf[VERY_BIG_BUF - 1] = '\0';
	new_draw_info(NDI_UNIQUE, op, buf);
	buf[0] = '\0';

	if (QUERY_FLAG(tmp, FLAG_MONSTER) || tmp->type == PLAYER)
	{
		new_draw_info_format(NDI_UNIQUE, op, "%s.", describe_item(tmp->head ? tmp->head : tmp));
		examine_living(op, tmp);
	}
	/* We don't double use the item_xxx arch commands, so they are always valid */
	else if (QUERY_FLAG(tmp, FLAG_IDENTIFIED))
	{
		if (QUERY_FLAG(tmp, FLAG_IS_GOOD))
		{
			new_draw_info_format(NDI_UNIQUE, op, "It is good aligned.");
		}
		else if (QUERY_FLAG(tmp, FLAG_IS_EVIL))
		{
			new_draw_info_format(NDI_UNIQUE, op, "It is evil aligned.");
		}
		else if (QUERY_FLAG(tmp, FLAG_IS_NEUTRAL))
		{
			new_draw_info_format(NDI_UNIQUE, op, "It is neutral aligned.");
		}

		if (tmp->item_level)
		{
			if (tmp->item_skill)
			{
				new_draw_info_format(NDI_UNIQUE, op, "It needs a level of %d in %s to use.", tmp->item_level, find_skill_exp_skillname(tmp->item_skill));
			}
			else
			{
				new_draw_info_format(NDI_UNIQUE, op, "It needs a level of %d to use.", tmp->item_level);
			}
		}

		if (tmp->item_quality)
		{
			if (QUERY_FLAG(tmp, FLAG_INDESTRUCTIBLE))
			{
				new_draw_info_format(NDI_UNIQUE, op, "Qua: %d Con: Indestructible.", tmp->item_quality);
			}
			else
			{
				new_draw_info_format(NDI_UNIQUE, op, "Qua: %d Con: %d.", tmp->item_quality, tmp->item_condition);
			}
		}

		buf[0] = '\0';
	}

	switch (tmp->type)
	{
		case SPELLBOOK:
			if (QUERY_FLAG(tmp, FLAG_IDENTIFIED) && tmp->stats.sp >= 0 && tmp->stats.sp <= NROFREALSPELLS)
			{
				if (tmp->sub_type == ST1_SPELLBOOK_CLERIC)
				{
					snprintf(buf, sizeof(buf), "%s is a %d level prayer.", spells[tmp->stats.sp].name, spells[tmp->stats.sp].level);
				}
				else
				{
					snprintf(buf, sizeof(buf), "%s is a %d level spell.", spells[tmp->stats.sp].name, spells[tmp->stats.sp].level);
				}
			}

			break;

		case BOOK:
			if (tmp->msg != NULL)
			{
				strcpy(buf, "Something is written in it.");
			}

			break;

		case CONTAINER:
			if (QUERY_FLAG(tmp, FLAG_IDENTIFIED))
			{
				if (tmp->race != NULL)
				{
					if (tmp->weight_limit)
					{
						snprintf(buf, sizeof(buf), "It can hold only %s and its weight limit is %.1f kg.", tmp->race, (float) tmp->weight_limit / 1000.0f);
					}
					else
					{
						snprintf(buf, sizeof(buf), "It can hold only %s.", tmp->race);
					}

					/* Has magic modifier? */
					if (tmp->weapon_speed != 1.0f)
					{
						new_draw_info(NDI_UNIQUE, op, buf);

						/* Bad */
						if (tmp->weapon_speed > 1.0f)
						{
							snprintf(buf, sizeof(buf), "It increases the weight of items inside by %.1f%%.", tmp->weapon_speed * 100.0f);
						}
						/* Good */
						else
						{
							snprintf(buf, sizeof(buf), "It decreases the weight of items inside by %.1f%%.", 100.0f - (tmp->weapon_speed * 100.0f));
						}
					}
				}
				else
				{
					if (tmp->weight_limit)
					{
						snprintf(buf, sizeof(buf), "Its weight limit is %.1f kg.", (float)tmp->weight_limit / 1000.0f);
					}

					/* Has magic modifier? */
					if (tmp->weapon_speed != 1.0f)
					{
						new_draw_info(NDI_UNIQUE, op, buf);

						/* Bad */
						if (tmp->weapon_speed > 1.0f)
						{
							snprintf(buf, sizeof(buf), "It increases the weight of items inside by %.1f%%.", tmp->weapon_speed * 100.0f);
						}
						/* Good */
						else
						{
							snprintf(buf, sizeof(buf), "It decreases the weight of items inside by %.1f%%.", 100.0f - (tmp->weapon_speed * 100.0f));
						}
					}
				}

				new_draw_info(NDI_UNIQUE, op, buf);

				if (tmp->weapon_speed == 1.0f)
				{
					snprintf(buf, sizeof(buf), "It contains %3.3f kg.", (float) tmp->carrying / 1000.0f);
				}
				else if (tmp->weapon_speed > 1.0f)
				{
					snprintf(buf, sizeof(buf), "It contains %3.3f kg, increased to %3.3f kg.", (float) tmp->damage_round_tag / 1000.0f, (float) tmp->carrying / 1000.0f);
				}
				else
				{
					snprintf(buf, sizeof(buf), "It contains %3.3f kg, decreased to %3.3f kg.", (float) tmp->damage_round_tag / 1000.0f, (float) tmp->carrying / 1000.0f);
				}
			}

			break;

		case WAND:
			if (QUERY_FLAG(tmp, FLAG_IDENTIFIED))
			{
				snprintf(buf, sizeof(buf), "It has %d charges left.", tmp->stats.food);
			}

			break;

		case POWER_CRYSTAL:
			/* Avoid division by zero... */
			if (tmp->stats.maxsp == 0)
			{
				snprintf(buf, sizeof(buf), "It has capacity of %d.", tmp->stats.maxsp);
			}
			else
			{
				int i;

				/* Higher capacity crystals */
				if (tmp->stats.maxsp > 1000)
				{
					i = (tmp->stats.maxsp % 1000) / 100;

					if (i)
					{
						snprintf(tmp_buf, sizeof(tmp_buf), "It has capacity of %d.%dk and is ", tmp->stats.maxsp / 1000, i);
					}
					else
					{
						snprintf(tmp_buf, sizeof(tmp_buf), "It has capacity of %dk and is ", tmp->stats.maxsp / 1000);
					}
				}
				else
				{
					snprintf(tmp_buf, sizeof(tmp_buf), "It has capacity of %d and is ", tmp->stats.maxsp);
				}

				strcat(buf, tmp_buf);
				i = (tmp->stats.sp * 10) / tmp->stats.maxsp;

				if (tmp->stats.sp == 0)
				{
					strcat(buf, "empty.");
				}
				else if (i == 0)
				{
					strcat(buf, "almost empty.");
				}
				else if (i < 3)
				{
					strcat(buf, "partially filled.");
				}
				else if (i < 6)
				{
					strcat(buf, "half full.");
				}
				else if (i < 9)
				{
					strcat(buf, "well charged.");
				}
				else if (tmp->stats.sp == tmp->stats.maxsp)
				{
					strcat(buf, "fully charged.");
				}
				else
				{
					strcat(buf, "almost full.");
				}
			}

			break;
	}

	if (buf[0] != '\0')
	{
		new_draw_info(NDI_UNIQUE, op, buf);
	}

	if (tmp->material && (need_identify(tmp) && QUERY_FLAG(tmp, FLAG_IDENTIFIED)))
	{
		strcpy(buf, "It is made of: ");

		for (i = 0; i < NROFMATERIALS; i++)
		{
			if (tmp->material & (1 << i))
			{
				strcat(buf, material[i].name);
				strcat(buf, " ");
			}
		}

		new_draw_info(NDI_UNIQUE, op, buf);
	}

	if (tmp->weight)
	{
		float weight = (float) (tmp->nrof ? tmp->weight * (int) tmp->nrof : tmp->weight) / 1000.0f;

		if (tmp->type == MONSTER)
		{
			new_draw_info_format(NDI_UNIQUE, op, "%s weighs %3.3f kg.", gender_subjective_upper[object_get_gender(tmp)], weight);
		}
		else if (tmp->type == PLAYER)
		{
			new_draw_info_format(NDI_UNIQUE, op, "%s weighs %3.3f kg and is carrying %3.3f kg.", gender_subjective_upper[object_get_gender(tmp)], weight, (float) tmp->carrying / 1000.0f);
		}
		else
		{
			new_draw_info_format(NDI_UNIQUE, op, tmp->nrof > 1 ? "They weigh %3.3f kg." : "It weighs %3.3f kg.", weight);
		}
	}

	if (QUERY_FLAG(tmp, FLAG_STARTEQUIP))
	{
		/* Unpaid clone shop item */
		if (QUERY_FLAG(tmp, FLAG_UNPAID))
		{
			new_draw_info_format(NDI_UNIQUE, op, "%s would cost you %s.", tmp->nrof > 1 ? "They" : "It", query_cost_string(tmp, op, F_BUY));
		}
		/* God-given item */
		else
		{
			new_draw_info_format(NDI_UNIQUE, op, "%s god-given item%s.", tmp->nrof > 1 ? "They are" : "It is a", tmp->nrof > 1 ? "s" : "");

			if (QUERY_FLAG(tmp, FLAG_IDENTIFIED))
			{
				if (tmp->value)
				{
					new_draw_info_format(NDI_UNIQUE, op, "But %s worth %s.", tmp->nrof > 1 ? "they are" : "it is", query_cost_string(tmp, op, F_TRUE));
				}
				else
				{
					new_draw_info_format(NDI_UNIQUE, op, "%s worthless.", tmp->nrof > 1 ? "They are" : "It is");
				}
			}
		}
	}
	else if (tmp->value && !IS_LIVE(tmp))
	{
		if (QUERY_FLAG(tmp, FLAG_IDENTIFIED))
		{
			if (QUERY_FLAG(tmp, FLAG_UNPAID))
			{
				new_draw_info_format(NDI_UNIQUE, op, "%s would cost you %s.", tmp->nrof > 1 ? "They" : "It", query_cost_string(tmp, op, F_BUY));
			}
			else
			{
				new_draw_info_format(NDI_UNIQUE, op, "%s worth %s.", tmp->nrof > 1 ? "They are" : "It is", query_cost_string(tmp, op, F_TRUE));
				goto dirty_little_jump1;
			}
		}
		else
		{
			object *floor;
dirty_little_jump1:

			floor = GET_MAP_OB_LAYER(op->map, op->x, op->y, 0);

			if (floor && floor->type == SHOP_FLOOR && tmp->type != MONEY)
			{
				/* Used for SK_BARGAINING modification */
				int charisma = op->stats.Cha;

				/* This skill gives us a charisma boost */
				if (find_skill(op, SK_BARGAINING))
				{
					charisma += 4;

					if (charisma > MAX_STAT)
					{
						charisma = MAX_STAT;
					}
				}

				new_draw_info_format(NDI_UNIQUE, op, "This shop will pay you %s (%0.1f%%).", query_cost_string(tmp, op, F_SELL), 20.0f + 100.0f * cha_bonus[charisma]);
			}
		}
	}
	else if (!IS_LIVE(tmp))
	{
		if (QUERY_FLAG(tmp, FLAG_IDENTIFIED))
		{
			if (QUERY_FLAG(tmp, FLAG_UNPAID))
			{
				new_draw_info_format(NDI_UNIQUE, op, "%s would cost nothing.", tmp->nrof > 1 ? "They" : "It");
			}
			else
			{
				new_draw_info_format(NDI_UNIQUE, op, "%s worthless.", tmp->nrof > 1 ? "They are" : "It is");
			}
		}
	}

	/* Does the object have a message?  Don't show message for all object
	 * types - especially if the first entry is a match */
	if (tmp->msg && tmp->type != EXIT && tmp->type != BOOK && tmp->type != CORPSE && !QUERY_FLAG(tmp, FLAG_WALK_ON) && strncasecmp(tmp->msg, "@match", 7))
	{
		/* This is just a hack so when identifying the items, we print
		 * out the extra message */
		if (need_identify(tmp) && QUERY_FLAG(tmp, FLAG_IDENTIFIED))
		{
			new_draw_info(NDI_UNIQUE, op, "The object has a story:");
			new_draw_info(NDI_UNIQUE, op, tmp->msg);
		}
	}

	/* Blank line */
	new_draw_info(NDI_UNIQUE, op, " ");

	if (QUERY_FLAG(op, FLAG_WIZ))
	{
		StringBuffer *sb = stringbuffer_new();
		char *diff;

		stringbuffer_append_printf(sb, "count %d\n", tmp->count);
		dump_object(tmp, sb);
		diff = stringbuffer_finish(sb);
		new_draw_info(NDI_UNIQUE, op, diff);
		free(diff);
	}
}