io_read_number no longer checks the buffer size. If use io_read_number_uc for that..
And then there is the code size: Using loops always duplicates the code inside the loop a few times, that was the major reason for the gotos for something that happens very seldom.
Second, io_avail very explicitly allowed negative numbers to be passed, removing all need to check for that in the functions calling it. It seems totally pointless to remove that support for no reason that I can see?
Third: You removed abstractions. Never do that, the intent of the io_* functions is that the implementation can be changed (as an example, always prefer io_len to io->len-io->offset).
Fourth: I got a bunch of warnings when compiling the code. That is not really a good thing, usually.
Fifth: In unread io_avail_rewind will be called with negative numbers if the function is passed negative numbers. That could cause havok. io_avail_throw does not, actually, throw in the general case. There was a reason it always was combined with something else.
Sixth: In read_hstring io_read_number can return negative numbers, especially on 32bit systems. You still has to check for that. It returns a LONGEST, which might be a 32bit int. So read_hstring( len>=sizeof(LONGEST) ) can easily break currently, causing a core dump.
There are more issues, but those are mainly related to making the code more complicated for no reason. Always trust the compiler to optimize simple expressions unless proven otherwise.
And, where, exactly, was this error in range checking?