The field's type picks the load
A field's C type fixes its size and its load instruction. The narrow unsigned types are the interesting case. An unsigned byte (u8) zero-extends through lbz (load byte zero-extend); a u16 does the same through lhz (load halfword zero-extend). The offset pins the position; the mnemonic encodes width and signedness.
Treat each mnemonic as evidence. An lbz at some offset says the field is a u8; an lhz says u16; neither is a plain int. One trap: don't model an unsigned byte as char. Its signedness is implementation-defined, so the compiler may add a stray extsb after the load.
The snippet below reads the third byte field of the Color struct, at offset 2:
typedef struct { u8 r; u8 g; u8 b; u8 a; } Color;
u8 Color_getB(Color* c) {
return c->b;
}
lbz r3,2(r3) # load c->b (offset 2)
blr
Offset 2 resolves to b, since r is byte 0 and g is byte 1. Apply the same reading to the target assembly to see which field its offset names.
Your task
With the Color struct above, write func_802102e0 to match the target.