bounds checking by doname() and xname()

Try harder to prevent buffer overflow when formatting objects.
I don't have any test cases where overflow has been happening so
don't really know whether this works reliably.  And it doesn't try
to check prefix construction by doname().  [Yet?]
This commit is contained in:
PatR
2024-01-20 17:53:44 -08:00
parent 414ee6eba7
commit 7b1ec30d0d
3 changed files with 295 additions and 160 deletions

View File

@@ -1085,6 +1085,12 @@ struct instance_globals_x {
/* mkmaze.c */
int xmin, xmax; /* level boundaries x */
/* objnam.c */
char *xnamep; /* obuf[] returned by xname(), for use in doname() for
* bounds checking; differs from xname() return value
* due to reserving PREFIX bytes at start and possibly
* skipping leading "the " after constructing result */
/* sp_lev.c */
coordxy xstart, xsize;

View File

@@ -882,9 +882,8 @@ const struct instance_globals_w g_init_w = {
UNDEFINED_PTR, /* wportal */
/* new */
{ wdmode_traditional, NO_COLOR }, /* wsettings */
TRUE, /* havestate*/
IVMAGIC /* w_magic used to validate that structure layout has been preserved */
IVMAGIC /* w_magic to validate that structure layout has been preserved */
};
const struct instance_globals_x g_init_x = {
@@ -895,6 +894,8 @@ const struct instance_globals_x g_init_x = {
/* mkmaze.c */
UNDEFINED_VALUE, /* xmin */
UNDEFINED_VALUE, /* xmax */
/* objnam.c */
NULL, /* xnamep */
/* sp_lev.c */
UNDEFINED_VALUE, /* xstart */
UNDEFINED_VALUE, /* xsize */

View File

@@ -66,6 +66,32 @@ struct Jitem {
#define BSTRNCMPI(base, ptr, str, num) \
((ptr) < base || strncmpi((ptr), str, num))
#define Strcasecpy(dst, src) (void) strcasecpy(dst, src)
#define Strncat(dst, src, cnt) (void) strncat(dst, src, cnt)
/* Concat(): append text to base, adjusted by delta, with bounds checking
via a pair of behind-the-scenes variables; delta is either 0 for normal
concatenation or 1 to replace the final character with something */
#define Concat(base, delta, text) \
do { \
Strncat(base ## _eos - delta, text, base ## spaceleft + delta); \
ConcUpdate(base); \
} while (0)
#define ConcatF1(base, delta, fmt, arg1) \
do { \
Snprintf(base ## _eos - delta, base ## spaceleft + delta, \
fmt, arg1); \
ConcUpdate(base); \
} while (0)
#define ConcatF2(base, delta, fmt, arg1, arg2) \
do { \
Snprintf(base ## _eos - delta, base ## spaceleft + delta, \
fmt, arg1, arg2); \
ConcUpdate(base); \
} while (0)
#define ConcUpdate(base) \
base ## _eos = eos(base ## _eos), \
/* convert signed ptrdiff_t to unsigned size_t */ \
base ## spaceleft = (size_t) (base ## _end - base ## _eos)
/* true for gems/rocks that should have " stone" appended to their names */
#define GemStone(typ) \
@@ -93,7 +119,7 @@ static const struct Jitem Japanese_items[] = {
};
static char *
strprepend(char *s,const char * pref)
strprepend(char *s, const char *pref)
{
register int i = (int) strlen(pref);
@@ -520,7 +546,7 @@ xcalled(
}
char *
xname(struct obj* obj)
xname(struct obj *obj)
{
return xname_flags(obj, CXN_NORMAL);
}
@@ -531,7 +557,8 @@ xname_flags(
unsigned cxn_flags) /* bitmask of CXN_xxx values */
{
register char *buf;
char *obufp;
char *obufp, *buf_end, *buf_eos;
size_t bufspaceleft;
int typ = obj->otyp;
struct objclass *ocl = &objects[typ];
int nn = ocl->oc_name_known, omndx = obj->corpsenm;
@@ -541,7 +568,14 @@ xname_flags(
boolean pluralize = (obj->quan != 1L) && !(cxn_flags & CXN_SINGULAR);
boolean known, dknown, bknown;
buf = nextobuf() + PREFIX; /* leave room for "17 -3 " */
gx.xnamep = nextobuf();
/* set up primary work buffer; the first 'PREFIX' bytes are set
aside for use by doname() */
buf = gx.xnamep + PREFIX; /* leave room for "17 -3 " */
buf_end = gx.xnamep + BUFSZ - 1; /* last byte within the obuf[] */
buf[0] = '\0', buf_eos = buf; /* eos(buf) for empty buf[] */
ConcUpdate(buf); /* update buf_eos (redundantly) and bufspaceleft */
if (Role_if(PM_SAMURAI)) {
actualn = Japanese_item_name(typ, actualn);
if (typ == WOODEN_HARP || typ == MAGIC_HARP)
@@ -556,7 +590,6 @@ xname_flags(
if (!dn)
dn = actualn;
buf[0] = '\0';
/*
* clean up known when it's tied to oc_name_known, eg after AD_DRIN
* This is only required for unique objects since the article
@@ -603,6 +636,12 @@ xname_flags(
if (obj_is_pname(obj))
goto nameit;
/* Some classes use strcpy(buf, something)+strcat(buf, otherthing).
In those cases, ConcUpdate() is needed in between if Concat()
will be used for the strcat() part. Other classes just use
strcpy(buf, something) and the ConcUpdate() can be deferred
until after the switch. */
switch (obj->oclass) {
case AMULET_CLASS:
if (!dknown)
@@ -623,6 +662,8 @@ xname_flags(
/*FALLTHRU*/
case VENOM_CLASS:
case TOOL_CLASS:
/* note: lenses or towel prefix would overwrite poisoned weapon
prefix if both were simultaneously posssible, but they aren't */
if (typ == LENSES)
Strcpy(buf, "pair of ");
else if (is_wet_towel(obj))
@@ -636,15 +677,16 @@ xname_flags(
xcalled(buf, BUFSZ - PREFIX, dn, un);
else
Strcat(buf, dn);
ConcUpdate(buf);
if (typ == FIGURINE && omndx != NON_PM) {
char anbuf[10]; /* [4] would be enough: 'a','n',' ','\0' */
const char *pm_name = obj_pmname(obj);
Sprintf(eos(buf), " of %s%s", just_an(anbuf, pm_name), pm_name);
ConcatF2(buf, 0, " of %s%s", just_an(anbuf, pm_name), pm_name);
} else if (is_wet_towel(obj)) {
if (wizard)
Sprintf(eos(buf), " (%d)", obj->spe);
ConcatF1(buf, 0, " (%d)", obj->spe);
}
break;
case ARMOR_CLASS:
@@ -652,11 +694,10 @@ xname_flags(
if (typ >= GRAY_DRAGON_SCALES && typ <= YELLOW_DRAGON_SCALES) {
Sprintf(buf, "set of %s", actualn);
break;
}
if (is_boots(obj) || is_gloves(obj))
} else if (is_boots(obj) || is_gloves(obj)) {
Strcpy(buf, "pair of ");
if (!dknown) {
/*FALLTHRU*/
} else if (is_shield(obj) && !dknown) {
if (obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD) {
Strcpy(buf, "shield");
break;
@@ -665,13 +706,14 @@ xname_flags(
break;
}
}
ConcUpdate(buf);
if (nn)
Strcat(buf, actualn);
Concat(buf, 0, actualn);
else if (un)
xcalled(buf, BUFSZ - PREFIX, armor_simple_name(obj), un);
else
Strcat(buf, dn);
Concat(buf, 0, dn);
break;
case FOOD_CLASS:
/* we could include partly-eaten-hack on fruit but don't need to */
@@ -682,6 +724,9 @@ xname_flags(
impossible("Bad fruit #%d?", obj->spe);
Strcpy(buf, "fruit");
} else {
/* fruit name is limited in length to PL_FSIZ; converting
to/from singular/plural might increase the length a
little but not enough to pose a risk of overflowing buf */
Strcpy(buf, f->fname);
if (pluralize) {
/* ick: already pluralized fruit names are allowed--we
@@ -706,18 +751,18 @@ xname_flags(
appropriate and omitted by xname(); shrink_glob() wants
it but uses Yname2() -> yname() -> xname() rather than
doname() so we've added an external flag to request it */
Strcat(buf, "partly eaten ");
Concat(buf, 0, "partly eaten ");
}
if (obj->globby) { /* 3.7 added "medium" to replace no-prefix */
Sprintf(eos(buf), "%s %s", (obj->owt <= 100) ? "small"
: (obj->owt <= 300) ? "medium"
: (obj->owt <= 500) ? "large"
: "very large",
actualn);
ConcatF2(buf, 0, "%s %s", (obj->owt <= 100) ? "small"
: (obj->owt <= 300) ? "medium"
: (obj->owt <= 500) ? "large"
: "very large",
actualn);
break;
}
Strcpy(buf, actualn);
Concat(buf, 0, actualn);
if (typ == TIN && known)
tin_details(obj, omndx, buf);
break;
@@ -730,24 +775,24 @@ xname_flags(
char anbuf[10];
const char *statue_pmname = obj_pmname(obj);
Sprintf(buf, "%s%s of %s%s",
(Role_if(PM_ARCHEOLOGIST)
&& (obj->spe & CORPSTAT_HISTORIC)) ? "historic " : "",
actualn,
type_is_pname(&mons[omndx]) ? ""
: the_unique_pm(&mons[omndx]) ? "the "
: just_an(anbuf, statue_pmname),
statue_pmname);
} else {
Snprintf(buf, bufspaceleft, "%s%s of %s%s",
(Role_if(PM_ARCHEOLOGIST)
&& (obj->spe & CORPSTAT_HISTORIC) != 0) ? "historic "
: "",
actualn,
type_is_pname(&mons[omndx]) ? ""
: the_unique_pm(&mons[omndx]) ? "the "
: just_an(anbuf, statue_pmname),
statue_pmname);
} else if (typ == BOULDER && obj->next_boulder == 1) {
/* sometimes caller wants "next boulder" rather than just
"boulder" (when pushing against a pile of more than one);
originally we just tested for non-0 but checking for 1 is
more robust because the default value for that overloaded
field (obj->corpsenm) is NON_PM (-1) rather than 0 */
if (typ == BOULDER && obj->next_boulder == 1)
Strcat(strcpy(buf, "next "), actualn);
else
Strcpy(buf, actualn);
Strcat(strcpy(buf, "next "), actualn); /* "next boulder" */
} else {
Strcpy(buf, actualn); /* "boulder" or "statue" */
}
break;
case BALL_CLASS:
@@ -852,20 +897,38 @@ xname_flags(
Strcat(buf, " stone");
}
break;
}
} /* gem */
default:
Sprintf(buf, "glorkum %d %d %d", obj->oclass, typ, obj->spe);
impossible("xname_flags: %s", buf);
break;
} /* switch */
/* check whether we've already gone out of bounds of the obuf[], prior
to pluralization and end-of-game shirt and apron text */
buf_eos = eos(buf);
if (buf_eos > buf_end) {
/* PREFIX is bigger than 6 so there will always be room within the
obuf[] in front of buf to insert "buf[]="; strncpy(,,N) doesn't
add '\0' terminator unless fewer than N chars are copied */
paniclog("xname", strncpy(buf - 6, "buf[]=", 6));
panic("xname: buffer overflow before appending name.");
/*NOTREACHED*/
}
bufspaceleft = (size_t) (buf_end - buf_eos);
/* if the name should be plural, do that now, after overflow check;
it could make buf[] become shorter */
if (pluralize) {
/* (see fruit name handling in case FOOD_CLASS above) */
Strcpy(buf, obufp = makeplural(buf));
obufp = makeplural(buf);
buf[0] = '\0', buf_eos = buf; /* replace the whole string */
ConcUpdate(buf);
Concat(buf, 0, obufp);
releaseobuf(obufp);
}
/* maybe give some extra information which isn't shown during play */
if (gp.program_state.gameover) {
if (gp.program_state.gameover && bufspaceleft > 0) {
const char *lbl;
char tmpbuf[BUFSZ];
@@ -874,18 +937,18 @@ xname_flags(
switch (obj->otyp) {
case T_SHIRT:
case ALCHEMY_SMOCK:
Sprintf(eos(buf), " with text \"%s\"",
(obj->otyp == T_SHIRT) ? tshirt_text(obj, tmpbuf)
: apron_text(obj, tmpbuf));
ConcatF1(buf, 0, " with text \"%s\"",
(obj->otyp == T_SHIRT) ? tshirt_text(obj, tmpbuf)
: apron_text(obj, tmpbuf));
break;
case CANDY_BAR:
lbl = candy_wrapper_text(obj);
if (*lbl)
Sprintf(eos(buf), " labeled \"%s\"", lbl);
ConcatF1(buf, 0, " labeled \"%s\"", lbl);
break;
case HAWAIIAN_SHIRT:
Sprintf(eos(buf), " with %s motif",
an(hawaiian_motif(obj, tmpbuf)));
ConcatF1(buf, 0, " with %s motif",
an(hawaiian_motif(obj, tmpbuf)));
break;
default:
break;
@@ -893,18 +956,35 @@ xname_flags(
}
if (has_oname(obj) && dknown) {
Strcat(buf, " named ");
Concat(buf, 0, " named ");
/* jump directly here if obj passes the has-personal-name test */
nameit:
obufp = eos(buf);
(void) strncat(buf, ONAME(obj),
BUFSZ - 1 - PREFIX - (unsigned) strlen(buf));
/*assert(has_oname(obj));*/
obufp = eos(buf); /* remember where the name will start */
Concat(buf, 0, ONAME(obj));
/* downcase "The" in "<quest-artifact-item> named The ..." */
if (obj->oartifact && !strncmp(obufp, "The ", 4))
*obufp = lowc(*obufp); /* = 't'; */
*obufp = lowc(*obufp); /* change 'T' in "The " to 't' */
}
if (!strncmpi(buf, "the ", 4))
buf += 4;
buf_eos = eos(buf); /* pointer to '\0' terminator somewhere in obuf[] */
if (buf_eos >= buf_end) { /* ('>' shouldn't be possible) */
static int xname_full = 0;
/* we want a record of something needing more buffer space than
anticipated; since we aren't panicking here, this could happen
repeatedly and we don't want to spam the paniclog file */
if (!xname_full++) {
paniclog("xname", strncpy(buf - 6, "buf[]=", 6));
/* 'PREFIX' ought to be 'PREFIX+4' if we stripped leading "the" */
paniclog("xname", "used up entire obuf[PREFIX..BUFSX-1]");
}
}
return buf;
}
@@ -965,7 +1045,7 @@ minimal_xname(struct obj *obj)
/* xname() output augmented for multishot missile feedback */
char *
mshot_xname(struct obj* obj)
mshot_xname(struct obj *obj)
{
char tmpbuf[BUFSZ];
char *onm = xname(obj);
@@ -981,7 +1061,7 @@ mshot_xname(struct obj* obj)
/* used for naming "the unique_item" instead of "a unique_item" */
boolean
the_unique_obj(struct obj* obj)
the_unique_obj(struct obj *obj)
{
boolean known = (obj->known || iflags.override_ID);
@@ -996,7 +1076,7 @@ the_unique_obj(struct obj* obj)
/* should monster type be prefixed with "the"? (mostly used for corpses) */
boolean
the_unique_pm(struct permonst* ptr)
the_unique_pm(struct permonst *ptr)
{
boolean uniq;
@@ -1018,7 +1098,7 @@ the_unique_pm(struct permonst* ptr)
}
static void
add_erosion_words(struct obj* obj, char* prefix)
add_erosion_words(struct obj *obj, char *prefix)
{
boolean iscrys = (obj->otyp == CRYSKNIFE);
boolean rknown;
@@ -1093,16 +1173,18 @@ erosion_matters(struct obj *obj)
#define DONAME_WITH_PRICE 1
#define DONAME_VAGUE_QUAN 2
#define DONAME_FOR_MENU 4 /* [not used anywhere yet] */
/* core of doname() */
static char *
doname_base(
struct obj* obj, /* object to format */
struct obj *obj, /* object to format */
unsigned doname_flags) /* special case requests */
{
boolean ispoisoned = FALSE,
with_price = (doname_flags & DONAME_WITH_PRICE) != 0,
vague_quan = (doname_flags & DONAME_VAGUE_QUAN) != 0;
vague_quan = (doname_flags & DONAME_VAGUE_QUAN) != 0,
for_menu = (doname_flags & DONAME_FOR_MENU) != 0;
boolean known, dknown, cknown, bknown, lknown,
fake_arti, force_the;
char prefix[PREFIX];
@@ -1111,7 +1193,20 @@ doname_base(
* end (Strcat is used on the end) */
const char *aname = 0;
int omndx = obj->corpsenm;
register char *bp = xname(obj);
register char *bp;
char *bp_eos, *bp_end;
size_t bpspaceleft;
/* 'bp' will be within an obuf[] rather than at the start of one,
usually (but not always) pointing at &obuf[PREFIX];
gx.xnamep always points to the start of that buffer;
'bp_eos' and 'bpspaceleft' are used and updated by Concat*() macros */
bp = xname(obj);
bp_end = gx.xnamep + BUFSZ - 1;
bp_eos = eos(bp);
assert(bp_end >= bp_eos); /* ok provided xname() bounds checking works */
/* size_t cast: convert signed ptrdiff_t to unsigned size_t */
bpspaceleft = (size_t) (bp_end - bp_eos);
if (iflags.override_ID) {
known = dknown = cknown = bknown = lknown = TRUE;
@@ -1130,7 +1225,7 @@ doname_base(
*/
/* must check opoisoned--someone can have a weirdly-named fruit */
if (!strncmp(bp, "poisoned ", 9) && obj->opoisoned) {
bp += 9;
bp += 9; /* doesn't affect bp_eos or bpspaceleft */
ispoisoned = TRUE;
}
@@ -1153,7 +1248,7 @@ doname_base(
;
} else if (force_the || obj_is_pname(obj) || the_unique_obj(obj)) {
if (!strncmpi(bp, "the ", 4))
bp += 4;
bp += 4; /* doesn't affect bp_eos or bpspaceleft */
Strcpy(prefix, "the ");
} else if (!fake_arti) {
/* default prefix */
@@ -1225,39 +1320,48 @@ doname_base(
if (obj->greased)
Strcat(prefix, "greased ");
if (cknown && Has_contents(obj)) {
if (cknown && Has_contents(obj) && bpspaceleft > 0) {
/* we count the number of separate stacks, which corresponds
to the number of inventory slots needed to be able to take
everything out if no merges occur */
long itemcount = count_contents(obj, FALSE, FALSE, TRUE, FALSE);
Sprintf(eos(bp), " containing %ld item%s", itemcount,
plur(itemcount));
ConcatF2(bp, 0, " containing %ld item%s", itemcount, plur(itemcount));
}
switch (is_weptool(obj) ? WEAPON_CLASS : obj->oclass) {
case AMULET_CLASS:
if (obj->owornmask & W_AMUL)
Strcat(bp, " (being worn)");
Concat(bp, 0, " (being worn)");
break;
case ARMOR_CLASS:
if (obj->owornmask & W_ARMOR) {
Strcat(bp, (obj == uskin) ? " (embedded in your skin)"
/* in case of perm_invent update while Wear/Takeoff
is in progress; check doffing() before donning()
because donning() returns True for both cases */
: doffing(obj) ? " (being doffed)"
: donning(obj) ? " (being donned)"
: " (being worn)");
/* slippery fingers is an intrinsic condition of the hero
rather than extrinsic condition of objects, but gloves
are described as slippery when hero has slippery fingers */
if (obj == uarmg && Glib) /* just appended "(something)",
* change to "(something; slippery)" */
Strcpy(strrchr(bp, ')'), "; slippery)");
else if (!Blind && obj->lamplit && artifact_light(obj))
Sprintf(strrchr(bp, ')'), ", %s lit)",
arti_light_description(obj));
Concat(bp, 0,
(obj == uskin) ? " (embedded in your skin)"
/* in case of perm_invent update while Wear/Takeoff
is in progress; check doffing() before donning()
because donning() returns True for both cases */
: doffing(obj) ? " (being doffed)"
: donning(obj) ? " (being donned)"
: " (being worn)");
/* we just added a parenthesized pharse, but the right paren
might be absent if the appended string got truncated */
if (bp_eos[-1] == ')') {
/* slippery fingers is an intrinsic condition of the hero
rather than extrinsic condition of objects, but gloves
are described as slippery when hero has slippery fingers */
if (obj == uarmg && Glib) /* just appended "(something)",
* replace paren, changing that
* to be "(something; slippery)" */
Concat(bp, 1, "; slippery)");
}
if (bp_eos[-1] == ')') {
/* there could be light-emitting artifact gloves someday,
so add 'lit' separately from 'slippery' rather than via
'else if' after uarmg+Glib */
if (!Blind && obj->lamplit && artifact_light(obj))
ConcatF1(bp, 1, ", %s lit)", arti_light_description(obj));
}
}
/*FALLTHRU*/
case WEAPON_CLASS:
@@ -1265,13 +1369,12 @@ doname_base(
Strcat(prefix, "poisoned ");
add_erosion_words(obj, prefix);
if (known) {
Strcat(prefix, sitoa(obj->spe));
Strcat(prefix, " ");
Sprintf(eos(prefix), "%+d ", obj->spe); /* sitoa(obj->spe)+" " */
}
break;
case TOOL_CLASS:
if (obj->owornmask & (W_TOOL | W_SADDLE)) { /* blindfold */
Strcat(bp, " (being worn)");
Concat(bp, 0, " (being worn)");
break;
}
if (obj->otyp == LEASH && obj->leashmon != 0) {
@@ -1281,15 +1384,17 @@ doname_base(
impossible("leashed monster not on this level");
obj->leashmon = 0;
} else {
Sprintf(eos(bp), " (attached to %s)",
noit_mon_nam(mlsh));
ConcatF1(bp, 0, " (attached to %s)", noit_mon_nam(mlsh));
}
break;
}
if (obj->otyp == CANDELABRUM_OF_INVOCATION) {
Sprintf(eos(bp), " (%d of 7 candle%s%s)",
obj->spe, plur(obj->spe),
char suffix[20]; /* longest value is "s attached" */
/* separately formatted suffix avoids need for ConcatF3() */
Sprintf(suffix, "%s%s", plur(obj->spe),
!obj->lamplit ? " attached" : ", lit");
ConcatF2(bp, 0, " (%d of 7 candle%s)", obj->spe, suffix);
break;
} else if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP
|| obj->otyp == BRASS_LANTERN || Is_candle(obj)) {
@@ -1313,7 +1418,7 @@ doname_base(
Strcat(prefix, "partly used ");
}
if (obj->lamplit)
Strcat(bp, " (lit)");
Concat(bp, 0, " (lit)");
break;
}
if (objects[obj->otyp].oc_charged)
@@ -1322,25 +1427,22 @@ doname_base(
case WAND_CLASS:
charges:
if (known)
Sprintf(eos(bp), " (%d:%d)", (int) obj->recharged, obj->spe);
ConcatF2(bp, 0, " (%d:%d)", (int) obj->recharged, obj->spe);
break;
case POTION_CLASS:
if (obj->otyp == POT_OIL && obj->lamplit)
Strcat(bp, " (lit)");
Concat(bp, 0, " (lit)");
break;
case RING_CLASS:
ring:
if (obj->owornmask & W_RINGR)
Strcat(bp, " (on right ");
Concat(bp, 0, " (on right ");
if (obj->owornmask & W_RINGL)
Strcat(bp, " (on left ");
if (obj->owornmask & W_RING) {
Strcat(bp, body_part(HAND));
Strcat(bp, ")");
}
Concat(bp, 0, " (on left ");
if (obj->owornmask & W_RING) /* either left or right */
ConcatF1(bp, 0,"%s)", body_part(HAND));
if (known && objects[obj->otyp].oc_charged) {
Strcat(prefix, sitoa(obj->spe));
Strcat(prefix, " ");
Sprintf(prefix, "%+d", obj->spe); /* sitoa(obj->spe)+" " */
}
break;
case FOOD_CLASS:
@@ -1367,7 +1469,7 @@ doname_base(
Strcat(prefix, mons[omndx].pmnames[NEUTRAL]);
Strcat(prefix, " ");
if (obj->spe == 1)
Strcat(bp, " (laid by you)");
Concat(bp, 0, " (laid by you)");
}
}
if (obj->otyp == MEAT_RING)
@@ -1377,8 +1479,8 @@ doname_base(
case CHAIN_CLASS:
add_erosion_words(obj, prefix);
if (obj->owornmask & (W_BALL | W_CHAIN))
Sprintf(eos(bp), " (%s to you)",
(obj->owornmask & W_BALL) ? "chained" : "attached");
ConcatF1(bp, 0, " (%s to you)",
(obj->owornmask & W_BALL) ? "chained" : "attached");
break;
}
@@ -1388,9 +1490,10 @@ doname_base(
mgend = ((cgend == CORPSTAT_MALE) ? MALE
: (cgend == CORPSTAT_FEMALE) ? FEMALE
: NEUTRAL);
Sprintf(eos(bp), " (%s)",
cgend != CORPSTAT_RANDOM ? genders[mgend].adj
: "unspecified gender");
ConcatF1(bp, 0, " (%s)",
(cgend != CORPSTAT_RANDOM) ? genders[mgend].adj
: "unspecified gender");
}
if ((obj->owornmask & W_WEP) && !gm.mrg_to_wielded) {
@@ -1408,83 +1511,78 @@ doname_base(
? (is_ammo(obj) || is_missile(obj))
: !is_weptool(obj)))
&& !twoweap_primary) {
Strcat(bp, " (wielded)");
Concat(bp, 0, " (wielded)");
} else {
const char *hand_s = body_part(HAND);
char *obufp, handsbuf[40];
if (bimanual(obj)) {
if (bimanual(obj)) { /* "hands" */
hand_s = strcpy(handsbuf, obufp = makeplural(hand_s));
releaseobuf(obufp);
} else { /* "right hand" or "left hand" */
Sprintf(handsbuf, "%s %s",
URIGHTY ? "right" : "left", hand_s);
hand_s = handsbuf;
}
/* note: Sting's glow message, if added, will insert text
in front of "(weapon in hand)"'s closing paren */
Sprintf(eos(bp), " (%s%s in %s%s)",
tethered ? "tethered " : "", /* aklys */
/* avoid "tethered wielded in right hand" for twoweapon */
(twoweap_primary && !tethered) ? "wielded" : "weapon",
bimanual(obj) ? "" : URIGHTY ? "right " : "left ",
/*
(twoweap_primary && URIGHTY)
? "right "
: (twoweap_primary && ULEFTY)
? "left "
: "",
*/
hand_s);
if (!Blind) {
ConcatF2(bp, 0, " (%s %s)",
tethered ? "tethered to"
: twoweap_primary ? "wielded in"
: "weapon in",
hand_s);
/* we just added a parenthesized pharse, but the right paren
might be absent if the appended string got truncated */
if (!Blind && bpspaceleft && bp_eos[-1] == ')') {
if (gw.warn_obj_cnt && obj == uwep
&& (EWarn_of_mon & W_WEP) != 0L)
/* we know bp[] ends with ')'; overwrite that */
Sprintf(eos(bp) - 1, ", %s %s)",
glow_verb(gw.warn_obj_cnt, TRUE),
glow_color(obj->oartifact));
ConcatF2(bp, 1, ", %s %s)",
glow_verb(gw.warn_obj_cnt, TRUE),
glow_color(obj->oartifact));
else if (obj->lamplit && artifact_light(obj))
/* as above, overwrite known closing paren */
Sprintf(eos(bp) - 1, ", %s lit)",
arti_light_description(obj));
ConcatF1(bp, 1, ", %s lit)",
arti_light_description(obj));
}
}
}
if (obj->owornmask & W_SWAPWEP) {
if (u.twoweap)
Sprintf(eos(bp), " (wielded in %s %s)",
URIGHTY ? "left" : "right", body_part(HAND));
ConcatF2(bp, 0, " (wielded in %s %s)",
URIGHTY ? "left" : "right", body_part(HAND));
else
/* TODO: rephrase this when obj isn't a weapon or weptool */
Sprintf(eos(bp), " (alternate weapon%s; not wielded)",
plur(obj->quan));
ConcatF1(bp, 0, " (alternate weapon%s; not wielded)",
plur(obj->quan));
}
if (obj->owornmask & W_QUIVER) {
int Qtyp;
switch (obj->oclass) {
case WEAPON_CLASS:
if (is_ammo(obj)) {
if (objects[obj->otyp].oc_skill == -P_BOW) {
/* Ammo for a bow */
Strcat(bp, " (in quiver)");
break;
} else {
/* Ammo not for a bow */
Strcat(bp, " (in quiver pouch)");
break;
}
} else {
/* Weapons not considered ammo */
Strcat(bp, " (at the ready)");
break;
}
/* Small things and ammo not for a bow */
Qtyp = !is_ammo(obj) ? 3 /* not ammo: "at the ready" */
: (objects[obj->otyp].oc_skill != -P_BOW) ? 2 /* non-bow */
: 1; /* ammo for a bow: "in quiver" */
break;
case RING_CLASS:
case AMULET_CLASS:
case WAND_CLASS:
case COIN_CLASS:
case GEM_CLASS:
Strcat(bp, " (in quiver pouch)");
Qtyp = 2; /* small, non-bow: "in quiver pouch" */
break;
default: /* odd things */
Strcat(bp, " (at the ready)");
Qtyp = 3; /* "at the ready" */
break;
}
ConcatF1(bp, 0, " (%s)",
(Qtyp == 1) ? "in quiver"
: (Qtyp == 2) ? "in quiver pouch"
: "at the ready");
}
/* treat 'restoring' like suppress_price because shopkeeper and
bill might not be available yet while restore is in progress
(objects won't normally be formatted during that time, but if
@@ -1492,22 +1590,28 @@ doname_base(
if (iflags.suppress_price || gp.program_state.restoring) {
; /* don't attempt to obtain any shop pricing, even if 'with_price' */
} else if (is_unpaid(obj)) { /* in inventory or in container in invent */
char pricebuf[40];
long quotedprice = unpaid_cost(obj, TRUE);
Sprintf(eos(bp), " (%s, %ld %s)",
obj->unpaid ? "unpaid" : "contents",
quotedprice, currency(quotedprice));
/* separately formatted suffix avoids need for ConcatF3() */
Sprintf(pricebuf, "%ld %s", quotedprice, currency(quotedprice));
ConcatF2(bp, 0, " (%s, %s)",
obj->unpaid ? "unpaid" : "contents", pricebuf);
} else if (with_price) { /* on floor or in container on floor */
int nochrg = 0;
long price = get_cost_of_shop_item(obj, &nochrg);
if (price > 0L)
Sprintf(eos(bp), " (%s, %ld %s)",
nochrg ? "contents" : "for sale",
price, currency(price));
else if (nochrg > 0)
Sprintf(eos(bp), " (no charge)");
if (price > 0L) {
char pricebuf[40];
Sprintf(pricebuf, "%ld %s", price, currency(price));
ConcatF2(bp, 0, " (%s, %s)",
nochrg ? "contents" : "for sale", pricebuf);
} else if (nochrg > 0) {
Concat(bp, 0, " (no charge)");
}
}
if (!strncmp(prefix, "a ", 2)) {
/* save current prefix, without "a "; might be empty */
Strcpy(tmpbuf, prefix + 2);
@@ -1521,12 +1625,33 @@ doname_base(
"aum" is stolen from Crawl's "Arbitrary Unit of Measure" */
if (wizard && iflags.wizweight) {
/* wizard mode user has asked to see object weights */
if (with_price && (*(eos(bp)-1) == ')'))
Sprintf(eos(bp)-1, ", %u aum)", obj->owt);
if (with_price && bp_eos[-1] == ')')
ConcatF1(bp, 1, ", %u aum)", obj->owt);
else
Sprintf(eos(bp), " (%u aum)", obj->owt);
ConcatF1(bp, 0, " (%u aum)", obj->owt);
/* ConcatF1(bp) updates bp_eos + bpspaceleft; we're done with them */
nhUse(bp_eos);
nhUse(bpspaceleft);
}
bp = strprepend(bp, prefix);
/*
* If caller intends this to be for a menu entry, make sure that
* there is some room to combine with menu selector prefix without
* exceeding BUFSZ-1.
*
* 4: width of menu entry selector text: "c - " for tty. For
* curses, that wastes a space since it only needs 3: "c) ".
*
* Reaching full BUFSZ-1 length can't happen unless both doname
* (BUFSZ-PREFIX) and strprepend (PREFIX) use up all available
* space.
*/
if (for_menu && strlen(bp) + 4 > BUFSZ - 1)
bp[BUFSZ - 1 - 4] = '\0';
return bp;
}
@@ -1606,8 +1731,7 @@ corpse_xname(
const char *adjective,
unsigned cxn_flags) /* bitmask of CXN_xxx values */
{
/* some callers [aobjnam()] rely on prefix area that xname() sets aside */
char *nambuf = nextobuf() + PREFIX;
char *nambuf;
int omndx = otmp->corpsenm;
boolean ignore_quan = (cxn_flags & CXN_SINGULAR) != 0,
/* suppress "the" from "the unique monster corpse" */
@@ -1622,6 +1746,10 @@ corpse_xname(
glob = (otmp->otyp != CORPSE && otmp->globby);
const char *mnam;
/* some callers [aobjnam()] rely on prefix area that xname() sets aside */
gx.xnamep = nextobuf();
nambuf = gx.xnamep + PREFIX;
if (glob) {
mnam = OBJ_NAME(objects[otmp->otyp]); /* "glob of <monster>" */
} else if (omndx == NON_PM) { /* paranoia */