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
+6
View File
@@ -1085,6 +1085,12 @@ struct instance_globals_x {
/* mkmaze.c */ /* mkmaze.c */
int xmin, xmax; /* level boundaries x */ 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 */ /* sp_lev.c */
coordxy xstart, xsize; coordxy xstart, xsize;
+3 -2
View File
@@ -882,9 +882,8 @@ const struct instance_globals_w g_init_w = {
UNDEFINED_PTR, /* wportal */ UNDEFINED_PTR, /* wportal */
/* new */ /* new */
{ wdmode_traditional, NO_COLOR }, /* wsettings */ { wdmode_traditional, NO_COLOR }, /* wsettings */
TRUE, /* havestate*/ 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 = { const struct instance_globals_x g_init_x = {
@@ -895,6 +894,8 @@ const struct instance_globals_x g_init_x = {
/* mkmaze.c */ /* mkmaze.c */
UNDEFINED_VALUE, /* xmin */ UNDEFINED_VALUE, /* xmin */
UNDEFINED_VALUE, /* xmax */ UNDEFINED_VALUE, /* xmax */
/* objnam.c */
NULL, /* xnamep */
/* sp_lev.c */ /* sp_lev.c */
UNDEFINED_VALUE, /* xstart */ UNDEFINED_VALUE, /* xstart */
UNDEFINED_VALUE, /* xsize */ UNDEFINED_VALUE, /* xsize */
+286 -158
View File
@@ -66,6 +66,32 @@ struct Jitem {
#define BSTRNCMPI(base, ptr, str, num) \ #define BSTRNCMPI(base, ptr, str, num) \
((ptr) < base || strncmpi((ptr), str, num)) ((ptr) < base || strncmpi((ptr), str, num))
#define Strcasecpy(dst, src) (void) strcasecpy(dst, src) #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 */ /* true for gems/rocks that should have " stone" appended to their names */
#define GemStone(typ) \ #define GemStone(typ) \
@@ -93,7 +119,7 @@ static const struct Jitem Japanese_items[] = {
}; };
static char * static char *
strprepend(char *s,const char * pref) strprepend(char *s, const char *pref)
{ {
register int i = (int) strlen(pref); register int i = (int) strlen(pref);
@@ -520,7 +546,7 @@ xcalled(
} }
char * char *
xname(struct obj* obj) xname(struct obj *obj)
{ {
return xname_flags(obj, CXN_NORMAL); return xname_flags(obj, CXN_NORMAL);
} }
@@ -531,7 +557,8 @@ xname_flags(
unsigned cxn_flags) /* bitmask of CXN_xxx values */ unsigned cxn_flags) /* bitmask of CXN_xxx values */
{ {
register char *buf; register char *buf;
char *obufp; char *obufp, *buf_end, *buf_eos;
size_t bufspaceleft;
int typ = obj->otyp; int typ = obj->otyp;
struct objclass *ocl = &objects[typ]; struct objclass *ocl = &objects[typ];
int nn = ocl->oc_name_known, omndx = obj->corpsenm; 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 pluralize = (obj->quan != 1L) && !(cxn_flags & CXN_SINGULAR);
boolean known, dknown, bknown; 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)) { if (Role_if(PM_SAMURAI)) {
actualn = Japanese_item_name(typ, actualn); actualn = Japanese_item_name(typ, actualn);
if (typ == WOODEN_HARP || typ == MAGIC_HARP) if (typ == WOODEN_HARP || typ == MAGIC_HARP)
@@ -556,7 +590,6 @@ xname_flags(
if (!dn) if (!dn)
dn = actualn; dn = actualn;
buf[0] = '\0';
/* /*
* clean up known when it's tied to oc_name_known, eg after AD_DRIN * 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 * This is only required for unique objects since the article
@@ -603,6 +636,12 @@ xname_flags(
if (obj_is_pname(obj)) if (obj_is_pname(obj))
goto nameit; 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) { switch (obj->oclass) {
case AMULET_CLASS: case AMULET_CLASS:
if (!dknown) if (!dknown)
@@ -623,6 +662,8 @@ xname_flags(
/*FALLTHRU*/ /*FALLTHRU*/
case VENOM_CLASS: case VENOM_CLASS:
case TOOL_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) if (typ == LENSES)
Strcpy(buf, "pair of "); Strcpy(buf, "pair of ");
else if (is_wet_towel(obj)) else if (is_wet_towel(obj))
@@ -636,15 +677,16 @@ xname_flags(
xcalled(buf, BUFSZ - PREFIX, dn, un); xcalled(buf, BUFSZ - PREFIX, dn, un);
else else
Strcat(buf, dn); Strcat(buf, dn);
ConcUpdate(buf);
if (typ == FIGURINE && omndx != NON_PM) { if (typ == FIGURINE && omndx != NON_PM) {
char anbuf[10]; /* [4] would be enough: 'a','n',' ','\0' */ char anbuf[10]; /* [4] would be enough: 'a','n',' ','\0' */
const char *pm_name = obj_pmname(obj); 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)) { } else if (is_wet_towel(obj)) {
if (wizard) if (wizard)
Sprintf(eos(buf), " (%d)", obj->spe); ConcatF1(buf, 0, " (%d)", obj->spe);
} }
break; break;
case ARMOR_CLASS: case ARMOR_CLASS:
@@ -652,11 +694,10 @@ xname_flags(
if (typ >= GRAY_DRAGON_SCALES && typ <= YELLOW_DRAGON_SCALES) { if (typ >= GRAY_DRAGON_SCALES && typ <= YELLOW_DRAGON_SCALES) {
Sprintf(buf, "set of %s", actualn); Sprintf(buf, "set of %s", actualn);
break; break;
} } else if (is_boots(obj) || is_gloves(obj)) {
if (is_boots(obj) || is_gloves(obj))
Strcpy(buf, "pair of "); Strcpy(buf, "pair of ");
/*FALLTHRU*/
if (!dknown) { } else if (is_shield(obj) && !dknown) {
if (obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD) { if (obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD) {
Strcpy(buf, "shield"); Strcpy(buf, "shield");
break; break;
@@ -665,13 +706,14 @@ xname_flags(
break; break;
} }
} }
ConcUpdate(buf);
if (nn) if (nn)
Strcat(buf, actualn); Concat(buf, 0, actualn);
else if (un) else if (un)
xcalled(buf, BUFSZ - PREFIX, armor_simple_name(obj), un); xcalled(buf, BUFSZ - PREFIX, armor_simple_name(obj), un);
else else
Strcat(buf, dn); Concat(buf, 0, dn);
break; break;
case FOOD_CLASS: case FOOD_CLASS:
/* we could include partly-eaten-hack on fruit but don't need to */ /* 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); impossible("Bad fruit #%d?", obj->spe);
Strcpy(buf, "fruit"); Strcpy(buf, "fruit");
} else { } 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); Strcpy(buf, f->fname);
if (pluralize) { if (pluralize) {
/* ick: already pluralized fruit names are allowed--we /* ick: already pluralized fruit names are allowed--we
@@ -706,18 +751,18 @@ xname_flags(
appropriate and omitted by xname(); shrink_glob() wants appropriate and omitted by xname(); shrink_glob() wants
it but uses Yname2() -> yname() -> xname() rather than it but uses Yname2() -> yname() -> xname() rather than
doname() so we've added an external flag to request it */ 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 */ if (obj->globby) { /* 3.7 added "medium" to replace no-prefix */
Sprintf(eos(buf), "%s %s", (obj->owt <= 100) ? "small" ConcatF2(buf, 0, "%s %s", (obj->owt <= 100) ? "small"
: (obj->owt <= 300) ? "medium" : (obj->owt <= 300) ? "medium"
: (obj->owt <= 500) ? "large" : (obj->owt <= 500) ? "large"
: "very large", : "very large",
actualn); actualn);
break; break;
} }
Strcpy(buf, actualn); Concat(buf, 0, actualn);
if (typ == TIN && known) if (typ == TIN && known)
tin_details(obj, omndx, buf); tin_details(obj, omndx, buf);
break; break;
@@ -730,24 +775,24 @@ xname_flags(
char anbuf[10]; char anbuf[10];
const char *statue_pmname = obj_pmname(obj); const char *statue_pmname = obj_pmname(obj);
Sprintf(buf, "%s%s of %s%s", Snprintf(buf, bufspaceleft, "%s%s of %s%s",
(Role_if(PM_ARCHEOLOGIST) (Role_if(PM_ARCHEOLOGIST)
&& (obj->spe & CORPSTAT_HISTORIC)) ? "historic " : "", && (obj->spe & CORPSTAT_HISTORIC) != 0) ? "historic "
actualn, : "",
type_is_pname(&mons[omndx]) ? "" actualn,
: the_unique_pm(&mons[omndx]) ? "the " type_is_pname(&mons[omndx]) ? ""
: just_an(anbuf, statue_pmname), : the_unique_pm(&mons[omndx]) ? "the "
statue_pmname); : just_an(anbuf, statue_pmname),
} else { statue_pmname);
} else if (typ == BOULDER && obj->next_boulder == 1) {
/* sometimes caller wants "next boulder" rather than just /* sometimes caller wants "next boulder" rather than just
"boulder" (when pushing against a pile of more than one); "boulder" (when pushing against a pile of more than one);
originally we just tested for non-0 but checking for 1 is originally we just tested for non-0 but checking for 1 is
more robust because the default value for that overloaded more robust because the default value for that overloaded
field (obj->corpsenm) is NON_PM (-1) rather than 0 */ field (obj->corpsenm) is NON_PM (-1) rather than 0 */
if (typ == BOULDER && obj->next_boulder == 1) Strcat(strcpy(buf, "next "), actualn); /* "next boulder" */
Strcat(strcpy(buf, "next "), actualn); } else {
else Strcpy(buf, actualn); /* "boulder" or "statue" */
Strcpy(buf, actualn);
} }
break; break;
case BALL_CLASS: case BALL_CLASS:
@@ -852,20 +897,38 @@ xname_flags(
Strcat(buf, " stone"); Strcat(buf, " stone");
} }
break; break;
} } /* gem */
default: default:
Sprintf(buf, "glorkum %d %d %d", obj->oclass, typ, obj->spe); Sprintf(buf, "glorkum %d %d %d", obj->oclass, typ, obj->spe);
impossible("xname_flags: %s", buf); impossible("xname_flags: %s", buf);
break; 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) { if (pluralize) {
/* (see fruit name handling in case FOOD_CLASS above) */ obufp = makeplural(buf);
Strcpy(buf, obufp = makeplural(buf)); buf[0] = '\0', buf_eos = buf; /* replace the whole string */
ConcUpdate(buf);
Concat(buf, 0, obufp);
releaseobuf(obufp); releaseobuf(obufp);
} }
/* maybe give some extra information which isn't shown during play */ /* 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; const char *lbl;
char tmpbuf[BUFSZ]; char tmpbuf[BUFSZ];
@@ -874,18 +937,18 @@ xname_flags(
switch (obj->otyp) { switch (obj->otyp) {
case T_SHIRT: case T_SHIRT:
case ALCHEMY_SMOCK: case ALCHEMY_SMOCK:
Sprintf(eos(buf), " with text \"%s\"", ConcatF1(buf, 0, " with text \"%s\"",
(obj->otyp == T_SHIRT) ? tshirt_text(obj, tmpbuf) (obj->otyp == T_SHIRT) ? tshirt_text(obj, tmpbuf)
: apron_text(obj, tmpbuf)); : apron_text(obj, tmpbuf));
break; break;
case CANDY_BAR: case CANDY_BAR:
lbl = candy_wrapper_text(obj); lbl = candy_wrapper_text(obj);
if (*lbl) if (*lbl)
Sprintf(eos(buf), " labeled \"%s\"", lbl); ConcatF1(buf, 0, " labeled \"%s\"", lbl);
break; break;
case HAWAIIAN_SHIRT: case HAWAIIAN_SHIRT:
Sprintf(eos(buf), " with %s motif", ConcatF1(buf, 0, " with %s motif",
an(hawaiian_motif(obj, tmpbuf))); an(hawaiian_motif(obj, tmpbuf)));
break; break;
default: default:
break; break;
@@ -893,18 +956,35 @@ xname_flags(
} }
if (has_oname(obj) && dknown) { if (has_oname(obj) && dknown) {
Strcat(buf, " named "); Concat(buf, 0, " named ");
/* jump directly here if obj passes the has-personal-name test */
nameit: nameit:
obufp = eos(buf); /*assert(has_oname(obj));*/
(void) strncat(buf, ONAME(obj), obufp = eos(buf); /* remember where the name will start */
BUFSZ - 1 - PREFIX - (unsigned) strlen(buf)); Concat(buf, 0, ONAME(obj));
/* downcase "The" in "<quest-artifact-item> named The ..." */ /* downcase "The" in "<quest-artifact-item> named The ..." */
if (obj->oartifact && !strncmp(obufp, "The ", 4)) 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)) if (!strncmpi(buf, "the ", 4))
buf += 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; return buf;
} }
@@ -965,7 +1045,7 @@ minimal_xname(struct obj *obj)
/* xname() output augmented for multishot missile feedback */ /* xname() output augmented for multishot missile feedback */
char * char *
mshot_xname(struct obj* obj) mshot_xname(struct obj *obj)
{ {
char tmpbuf[BUFSZ]; char tmpbuf[BUFSZ];
char *onm = xname(obj); char *onm = xname(obj);
@@ -981,7 +1061,7 @@ mshot_xname(struct obj* obj)
/* used for naming "the unique_item" instead of "a unique_item" */ /* used for naming "the unique_item" instead of "a unique_item" */
boolean boolean
the_unique_obj(struct obj* obj) the_unique_obj(struct obj *obj)
{ {
boolean known = (obj->known || iflags.override_ID); 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) */ /* should monster type be prefixed with "the"? (mostly used for corpses) */
boolean boolean
the_unique_pm(struct permonst* ptr) the_unique_pm(struct permonst *ptr)
{ {
boolean uniq; boolean uniq;
@@ -1018,7 +1098,7 @@ the_unique_pm(struct permonst* ptr)
} }
static void static void
add_erosion_words(struct obj* obj, char* prefix) add_erosion_words(struct obj *obj, char *prefix)
{ {
boolean iscrys = (obj->otyp == CRYSKNIFE); boolean iscrys = (obj->otyp == CRYSKNIFE);
boolean rknown; boolean rknown;
@@ -1093,16 +1173,18 @@ erosion_matters(struct obj *obj)
#define DONAME_WITH_PRICE 1 #define DONAME_WITH_PRICE 1
#define DONAME_VAGUE_QUAN 2 #define DONAME_VAGUE_QUAN 2
#define DONAME_FOR_MENU 4 /* [not used anywhere yet] */
/* core of doname() */ /* core of doname() */
static char * static char *
doname_base( doname_base(
struct obj* obj, /* object to format */ struct obj *obj, /* object to format */
unsigned doname_flags) /* special case requests */ unsigned doname_flags) /* special case requests */
{ {
boolean ispoisoned = FALSE, boolean ispoisoned = FALSE,
with_price = (doname_flags & DONAME_WITH_PRICE) != 0, 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, boolean known, dknown, cknown, bknown, lknown,
fake_arti, force_the; fake_arti, force_the;
char prefix[PREFIX]; char prefix[PREFIX];
@@ -1111,7 +1193,20 @@ doname_base(
* end (Strcat is used on the end) */ * end (Strcat is used on the end) */
const char *aname = 0; const char *aname = 0;
int omndx = obj->corpsenm; 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) { if (iflags.override_ID) {
known = dknown = cknown = bknown = lknown = TRUE; known = dknown = cknown = bknown = lknown = TRUE;
@@ -1130,7 +1225,7 @@ doname_base(
*/ */
/* must check opoisoned--someone can have a weirdly-named fruit */ /* must check opoisoned--someone can have a weirdly-named fruit */
if (!strncmp(bp, "poisoned ", 9) && obj->opoisoned) { if (!strncmp(bp, "poisoned ", 9) && obj->opoisoned) {
bp += 9; bp += 9; /* doesn't affect bp_eos or bpspaceleft */
ispoisoned = TRUE; ispoisoned = TRUE;
} }
@@ -1153,7 +1248,7 @@ doname_base(
; ;
} else if (force_the || obj_is_pname(obj) || the_unique_obj(obj)) { } else if (force_the || obj_is_pname(obj) || the_unique_obj(obj)) {
if (!strncmpi(bp, "the ", 4)) if (!strncmpi(bp, "the ", 4))
bp += 4; bp += 4; /* doesn't affect bp_eos or bpspaceleft */
Strcpy(prefix, "the "); Strcpy(prefix, "the ");
} else if (!fake_arti) { } else if (!fake_arti) {
/* default prefix */ /* default prefix */
@@ -1225,39 +1320,48 @@ doname_base(
if (obj->greased) if (obj->greased)
Strcat(prefix, "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 /* we count the number of separate stacks, which corresponds
to the number of inventory slots needed to be able to take to the number of inventory slots needed to be able to take
everything out if no merges occur */ everything out if no merges occur */
long itemcount = count_contents(obj, FALSE, FALSE, TRUE, FALSE); long itemcount = count_contents(obj, FALSE, FALSE, TRUE, FALSE);
Sprintf(eos(bp), " containing %ld item%s", itemcount, ConcatF2(bp, 0, " containing %ld item%s", itemcount, plur(itemcount));
plur(itemcount));
} }
switch (is_weptool(obj) ? WEAPON_CLASS : obj->oclass) { switch (is_weptool(obj) ? WEAPON_CLASS : obj->oclass) {
case AMULET_CLASS: case AMULET_CLASS:
if (obj->owornmask & W_AMUL) if (obj->owornmask & W_AMUL)
Strcat(bp, " (being worn)"); Concat(bp, 0, " (being worn)");
break; break;
case ARMOR_CLASS: case ARMOR_CLASS:
if (obj->owornmask & W_ARMOR) { if (obj->owornmask & W_ARMOR) {
Strcat(bp, (obj == uskin) ? " (embedded in your skin)" Concat(bp, 0,
/* in case of perm_invent update while Wear/Takeoff (obj == uskin) ? " (embedded in your skin)"
is in progress; check doffing() before donning() /* in case of perm_invent update while Wear/Takeoff
because donning() returns True for both cases */ is in progress; check doffing() before donning()
: doffing(obj) ? " (being doffed)" because donning() returns True for both cases */
: donning(obj) ? " (being donned)" : doffing(obj) ? " (being doffed)"
: " (being worn)"); : donning(obj) ? " (being donned)"
/* slippery fingers is an intrinsic condition of the hero : " (being worn)");
rather than extrinsic condition of objects, but gloves /* we just added a parenthesized pharse, but the right paren
are described as slippery when hero has slippery fingers */ might be absent if the appended string got truncated */
if (obj == uarmg && Glib) /* just appended "(something)", if (bp_eos[-1] == ')') {
* change to "(something; slippery)" */ /* slippery fingers is an intrinsic condition of the hero
Strcpy(strrchr(bp, ')'), "; slippery)"); rather than extrinsic condition of objects, but gloves
else if (!Blind && obj->lamplit && artifact_light(obj)) are described as slippery when hero has slippery fingers */
Sprintf(strrchr(bp, ')'), ", %s lit)", if (obj == uarmg && Glib) /* just appended "(something)",
arti_light_description(obj)); * 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*/ /*FALLTHRU*/
case WEAPON_CLASS: case WEAPON_CLASS:
@@ -1265,13 +1369,12 @@ doname_base(
Strcat(prefix, "poisoned "); Strcat(prefix, "poisoned ");
add_erosion_words(obj, prefix); add_erosion_words(obj, prefix);
if (known) { if (known) {
Strcat(prefix, sitoa(obj->spe)); Sprintf(eos(prefix), "%+d ", obj->spe); /* sitoa(obj->spe)+" " */
Strcat(prefix, " ");
} }
break; break;
case TOOL_CLASS: case TOOL_CLASS:
if (obj->owornmask & (W_TOOL | W_SADDLE)) { /* blindfold */ if (obj->owornmask & (W_TOOL | W_SADDLE)) { /* blindfold */
Strcat(bp, " (being worn)"); Concat(bp, 0, " (being worn)");
break; break;
} }
if (obj->otyp == LEASH && obj->leashmon != 0) { if (obj->otyp == LEASH && obj->leashmon != 0) {
@@ -1281,15 +1384,17 @@ doname_base(
impossible("leashed monster not on this level"); impossible("leashed monster not on this level");
obj->leashmon = 0; obj->leashmon = 0;
} else { } else {
Sprintf(eos(bp), " (attached to %s)", ConcatF1(bp, 0, " (attached to %s)", noit_mon_nam(mlsh));
noit_mon_nam(mlsh));
} }
break; break;
} }
if (obj->otyp == CANDELABRUM_OF_INVOCATION) { if (obj->otyp == CANDELABRUM_OF_INVOCATION) {
Sprintf(eos(bp), " (%d of 7 candle%s%s)", char suffix[20]; /* longest value is "s attached" */
obj->spe, plur(obj->spe),
/* separately formatted suffix avoids need for ConcatF3() */
Sprintf(suffix, "%s%s", plur(obj->spe),
!obj->lamplit ? " attached" : ", lit"); !obj->lamplit ? " attached" : ", lit");
ConcatF2(bp, 0, " (%d of 7 candle%s)", obj->spe, suffix);
break; break;
} else if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP } else if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP
|| obj->otyp == BRASS_LANTERN || Is_candle(obj)) { || obj->otyp == BRASS_LANTERN || Is_candle(obj)) {
@@ -1313,7 +1418,7 @@ doname_base(
Strcat(prefix, "partly used "); Strcat(prefix, "partly used ");
} }
if (obj->lamplit) if (obj->lamplit)
Strcat(bp, " (lit)"); Concat(bp, 0, " (lit)");
break; break;
} }
if (objects[obj->otyp].oc_charged) if (objects[obj->otyp].oc_charged)
@@ -1322,25 +1427,22 @@ doname_base(
case WAND_CLASS: case WAND_CLASS:
charges: charges:
if (known) if (known)
Sprintf(eos(bp), " (%d:%d)", (int) obj->recharged, obj->spe); ConcatF2(bp, 0, " (%d:%d)", (int) obj->recharged, obj->spe);
break; break;
case POTION_CLASS: case POTION_CLASS:
if (obj->otyp == POT_OIL && obj->lamplit) if (obj->otyp == POT_OIL && obj->lamplit)
Strcat(bp, " (lit)"); Concat(bp, 0, " (lit)");
break; break;
case RING_CLASS: case RING_CLASS:
ring: ring:
if (obj->owornmask & W_RINGR) if (obj->owornmask & W_RINGR)
Strcat(bp, " (on right "); Concat(bp, 0, " (on right ");
if (obj->owornmask & W_RINGL) if (obj->owornmask & W_RINGL)
Strcat(bp, " (on left "); Concat(bp, 0, " (on left ");
if (obj->owornmask & W_RING) { if (obj->owornmask & W_RING) /* either left or right */
Strcat(bp, body_part(HAND)); ConcatF1(bp, 0,"%s)", body_part(HAND));
Strcat(bp, ")");
}
if (known && objects[obj->otyp].oc_charged) { if (known && objects[obj->otyp].oc_charged) {
Strcat(prefix, sitoa(obj->spe)); Sprintf(prefix, "%+d", obj->spe); /* sitoa(obj->spe)+" " */
Strcat(prefix, " ");
} }
break; break;
case FOOD_CLASS: case FOOD_CLASS:
@@ -1367,7 +1469,7 @@ doname_base(
Strcat(prefix, mons[omndx].pmnames[NEUTRAL]); Strcat(prefix, mons[omndx].pmnames[NEUTRAL]);
Strcat(prefix, " "); Strcat(prefix, " ");
if (obj->spe == 1) if (obj->spe == 1)
Strcat(bp, " (laid by you)"); Concat(bp, 0, " (laid by you)");
} }
} }
if (obj->otyp == MEAT_RING) if (obj->otyp == MEAT_RING)
@@ -1377,8 +1479,8 @@ doname_base(
case CHAIN_CLASS: case CHAIN_CLASS:
add_erosion_words(obj, prefix); add_erosion_words(obj, prefix);
if (obj->owornmask & (W_BALL | W_CHAIN)) if (obj->owornmask & (W_BALL | W_CHAIN))
Sprintf(eos(bp), " (%s to you)", ConcatF1(bp, 0, " (%s to you)",
(obj->owornmask & W_BALL) ? "chained" : "attached"); (obj->owornmask & W_BALL) ? "chained" : "attached");
break; break;
} }
@@ -1388,9 +1490,10 @@ doname_base(
mgend = ((cgend == CORPSTAT_MALE) ? MALE mgend = ((cgend == CORPSTAT_MALE) ? MALE
: (cgend == CORPSTAT_FEMALE) ? FEMALE : (cgend == CORPSTAT_FEMALE) ? FEMALE
: NEUTRAL); : NEUTRAL);
Sprintf(eos(bp), " (%s)",
cgend != CORPSTAT_RANDOM ? genders[mgend].adj ConcatF1(bp, 0, " (%s)",
: "unspecified gender"); (cgend != CORPSTAT_RANDOM) ? genders[mgend].adj
: "unspecified gender");
} }
if ((obj->owornmask & W_WEP) && !gm.mrg_to_wielded) { if ((obj->owornmask & W_WEP) && !gm.mrg_to_wielded) {
@@ -1408,83 +1511,78 @@ doname_base(
? (is_ammo(obj) || is_missile(obj)) ? (is_ammo(obj) || is_missile(obj))
: !is_weptool(obj))) : !is_weptool(obj)))
&& !twoweap_primary) { && !twoweap_primary) {
Strcat(bp, " (wielded)"); Concat(bp, 0, " (wielded)");
} else { } else {
const char *hand_s = body_part(HAND); const char *hand_s = body_part(HAND);
char *obufp, handsbuf[40]; char *obufp, handsbuf[40];
if (bimanual(obj)) { if (bimanual(obj)) { /* "hands" */
hand_s = strcpy(handsbuf, obufp = makeplural(hand_s)); hand_s = strcpy(handsbuf, obufp = makeplural(hand_s));
releaseobuf(obufp); 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 /* note: Sting's glow message, if added, will insert text
in front of "(weapon in hand)"'s closing paren */ in front of "(weapon in hand)"'s closing paren */
Sprintf(eos(bp), " (%s%s in %s%s)", ConcatF2(bp, 0, " (%s %s)",
tethered ? "tethered " : "", /* aklys */ tethered ? "tethered to"
/* avoid "tethered wielded in right hand" for twoweapon */ : twoweap_primary ? "wielded in"
(twoweap_primary && !tethered) ? "wielded" : "weapon", : "weapon in",
bimanual(obj) ? "" : URIGHTY ? "right " : "left ", hand_s);
/*
(twoweap_primary && URIGHTY) /* we just added a parenthesized pharse, but the right paren
? "right " might be absent if the appended string got truncated */
: (twoweap_primary && ULEFTY) if (!Blind && bpspaceleft && bp_eos[-1] == ')') {
? "left "
: "",
*/
hand_s);
if (!Blind) {
if (gw.warn_obj_cnt && obj == uwep if (gw.warn_obj_cnt && obj == uwep
&& (EWarn_of_mon & W_WEP) != 0L) && (EWarn_of_mon & W_WEP) != 0L)
/* we know bp[] ends with ')'; overwrite that */ /* we know bp[] ends with ')'; overwrite that */
Sprintf(eos(bp) - 1, ", %s %s)", ConcatF2(bp, 1, ", %s %s)",
glow_verb(gw.warn_obj_cnt, TRUE), glow_verb(gw.warn_obj_cnt, TRUE),
glow_color(obj->oartifact)); glow_color(obj->oartifact));
else if (obj->lamplit && artifact_light(obj)) else if (obj->lamplit && artifact_light(obj))
/* as above, overwrite known closing paren */ /* as above, overwrite known closing paren */
Sprintf(eos(bp) - 1, ", %s lit)", ConcatF1(bp, 1, ", %s lit)",
arti_light_description(obj)); arti_light_description(obj));
} }
} }
} }
if (obj->owornmask & W_SWAPWEP) { if (obj->owornmask & W_SWAPWEP) {
if (u.twoweap) if (u.twoweap)
Sprintf(eos(bp), " (wielded in %s %s)", ConcatF2(bp, 0, " (wielded in %s %s)",
URIGHTY ? "left" : "right", body_part(HAND)); URIGHTY ? "left" : "right", body_part(HAND));
else else
/* TODO: rephrase this when obj isn't a weapon or weptool */ /* TODO: rephrase this when obj isn't a weapon or weptool */
Sprintf(eos(bp), " (alternate weapon%s; not wielded)", ConcatF1(bp, 0, " (alternate weapon%s; not wielded)",
plur(obj->quan)); plur(obj->quan));
} }
if (obj->owornmask & W_QUIVER) { if (obj->owornmask & W_QUIVER) {
int Qtyp;
switch (obj->oclass) { switch (obj->oclass) {
case WEAPON_CLASS: case WEAPON_CLASS:
if (is_ammo(obj)) { Qtyp = !is_ammo(obj) ? 3 /* not ammo: "at the ready" */
if (objects[obj->otyp].oc_skill == -P_BOW) { : (objects[obj->otyp].oc_skill != -P_BOW) ? 2 /* non-bow */
/* Ammo for a bow */ : 1; /* ammo for a bow: "in quiver" */
Strcat(bp, " (in quiver)"); break;
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 */
case RING_CLASS: case RING_CLASS:
case AMULET_CLASS: case AMULET_CLASS:
case WAND_CLASS: case WAND_CLASS:
case COIN_CLASS: case COIN_CLASS:
case GEM_CLASS: case GEM_CLASS:
Strcat(bp, " (in quiver pouch)"); Qtyp = 2; /* small, non-bow: "in quiver pouch" */
break; break;
default: /* odd things */ 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 /* treat 'restoring' like suppress_price because shopkeeper and
bill might not be available yet while restore is in progress bill might not be available yet while restore is in progress
(objects won't normally be formatted during that time, but if (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) { if (iflags.suppress_price || gp.program_state.restoring) {
; /* don't attempt to obtain any shop pricing, even if 'with_price' */ ; /* don't attempt to obtain any shop pricing, even if 'with_price' */
} else if (is_unpaid(obj)) { /* in inventory or in container in invent */ } else if (is_unpaid(obj)) { /* in inventory or in container in invent */
char pricebuf[40];
long quotedprice = unpaid_cost(obj, TRUE); long quotedprice = unpaid_cost(obj, TRUE);
Sprintf(eos(bp), " (%s, %ld %s)", /* separately formatted suffix avoids need for ConcatF3() */
obj->unpaid ? "unpaid" : "contents", Sprintf(pricebuf, "%ld %s", quotedprice, currency(quotedprice));
quotedprice, currency(quotedprice)); ConcatF2(bp, 0, " (%s, %s)",
obj->unpaid ? "unpaid" : "contents", pricebuf);
} else if (with_price) { /* on floor or in container on floor */ } else if (with_price) { /* on floor or in container on floor */
int nochrg = 0; int nochrg = 0;
long price = get_cost_of_shop_item(obj, &nochrg); long price = get_cost_of_shop_item(obj, &nochrg);
if (price > 0L) if (price > 0L) {
Sprintf(eos(bp), " (%s, %ld %s)", char pricebuf[40];
nochrg ? "contents" : "for sale",
price, currency(price)); Sprintf(pricebuf, "%ld %s", price, currency(price));
else if (nochrg > 0) ConcatF2(bp, 0, " (%s, %s)",
Sprintf(eos(bp), " (no charge)"); nochrg ? "contents" : "for sale", pricebuf);
} else if (nochrg > 0) {
Concat(bp, 0, " (no charge)");
}
} }
if (!strncmp(prefix, "a ", 2)) { if (!strncmp(prefix, "a ", 2)) {
/* save current prefix, without "a "; might be empty */ /* save current prefix, without "a "; might be empty */
Strcpy(tmpbuf, prefix + 2); Strcpy(tmpbuf, prefix + 2);
@@ -1521,12 +1625,33 @@ doname_base(
"aum" is stolen from Crawl's "Arbitrary Unit of Measure" */ "aum" is stolen from Crawl's "Arbitrary Unit of Measure" */
if (wizard && iflags.wizweight) { if (wizard && iflags.wizweight) {
/* wizard mode user has asked to see object weights */ /* wizard mode user has asked to see object weights */
if (with_price && (*(eos(bp)-1) == ')')) if (with_price && bp_eos[-1] == ')')
Sprintf(eos(bp)-1, ", %u aum)", obj->owt); ConcatF1(bp, 1, ", %u aum)", obj->owt);
else 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); 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; return bp;
} }
@@ -1606,8 +1731,7 @@ corpse_xname(
const char *adjective, const char *adjective,
unsigned cxn_flags) /* bitmask of CXN_xxx values */ unsigned cxn_flags) /* bitmask of CXN_xxx values */
{ {
/* some callers [aobjnam()] rely on prefix area that xname() sets aside */ char *nambuf;
char *nambuf = nextobuf() + PREFIX;
int omndx = otmp->corpsenm; int omndx = otmp->corpsenm;
boolean ignore_quan = (cxn_flags & CXN_SINGULAR) != 0, boolean ignore_quan = (cxn_flags & CXN_SINGULAR) != 0,
/* suppress "the" from "the unique monster corpse" */ /* suppress "the" from "the unique monster corpse" */
@@ -1622,6 +1746,10 @@ corpse_xname(
glob = (otmp->otyp != CORPSE && otmp->globby); glob = (otmp->otyp != CORPSE && otmp->globby);
const char *mnam; const char *mnam;
/* some callers [aobjnam()] rely on prefix area that xname() sets aside */
gx.xnamep = nextobuf();
nambuf = gx.xnamep + PREFIX;
if (glob) { if (glob) {
mnam = OBJ_NAME(objects[otmp->otyp]); /* "glob of <monster>" */ mnam = OBJ_NAME(objects[otmp->otyp]); /* "glob of <monster>" */
} else if (omndx == NON_PM) { /* paranoia */ } else if (omndx == NON_PM) { /* paranoia */