Transaction outputs have a "value" field that's an 8-byte signed integer (always 8 bytes) indicating how many satoshis (0.00000001 BTC each) are available for that output. That is the thing that gets set to -1 on the other outputs in SIGHASH_SINGLE mode.
This looks to be the code responsible for making this happen, from script.cpp:
/** Serialize an output of txTo */
template<typename S>
void SerializeOutput(S &s, unsigned int nOutput, int nType, int nVersion) const {
if (fHashSingle && nOutput != nIn)
// Do not lock-in the txout payee at other indices as txin
::Serialize(s, CTxOut(), nType, nVersion);
else
::Serialize(s, txTo.vout[nOutput], nType, nVersion);
}
If we're in SIGHASH_SINGLE mode, and the index of the output is different from the index of the input, then the thing that we sign in this spot is just a dummy CTxOut object, whose parameterless constructor (in core.h) calls SetNull(), which sets the field called "nValue" to -1 (-0.00000001 BTC) and clears the scriptPubKey, which ensures that it's a 0-byte vector.
When CTxOut gets serialized, it writes the nValue and scriptPubKey per this (core.h again):
IMPLEMENT_SERIALIZE
(
READWRITE(nValue);
READWRITE(scriptPubKey);
)
nValue serializes as the 8-byte twos-complement representation of -1, or 0xFFFFFFFFFFFFFFFF.
scriptPubKey serializes as a 1-byte CompactSize that indicates that the script is 0 bytes long, or 0x0.