Skip to content

Commit 91bbdb1

Browse files
authored
Merge pull request ferrumc-rs#286 from NanCunChild/fix/chunk-index-out-bound
Fix crash on chunk palette resize (>16 blocks) and correct data packing logic
2 parents 344ff72 + 35bb42d commit 91bbdb1

File tree

1 file changed

+69
-50
lines changed

1 file changed

+69
-50
lines changed

src/lib/world/src/edits.rs

Lines changed: 69 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -103,78 +103,81 @@ impl BlockStates {
103103
data,
104104
palette,
105105
} => {
106-
// Step 1: Read existing packed data into a list of normal integers
107106
let mut normalised_ints = Vec::with_capacity(4096);
108107
let mut values_read = 0;
109108

110109
for long in data {
111110
let mut bit_offset = 0;
112-
113111
while bit_offset + *bits_per_block as usize <= 64 {
114112
if values_read >= 4096 {
115113
break;
116114
}
117115

118-
// Extract value at the current bit offset
119116
let value =
120117
read_nbit_i32(long, *bits_per_block as usize, bit_offset as u32)?;
118+
121119
let max_int_value = (1 << new_bit_size) - 1;
122120
if value > max_int_value {
123121
return Err(WorldError::InvalidBlockStateData(format!(
124122
"Value {value} exceeds maximum value for {new_bit_size}-bit block state"
125123
)));
126124
}
125+
127126
normalised_ints.push(value);
128127
values_read += 1;
129-
130128
bit_offset += *bits_per_block as usize;
131129
}
132-
133-
// Stop reading if we’ve already hit 4096 values
134130
if values_read >= 4096 {
135131
break;
136132
}
137133
}
138134

139-
// Check if we read exactly 4096 block states
140135
if normalised_ints.len() != 4096 {
141136
return Err(WorldError::InvalidBlockStateData(format!(
142137
"Expected 4096 block states, but got {}",
143138
normalised_ints.len()
144139
)));
145140
}
146141

147-
// Step 2: Write the normalised integers into the new packed format
148142
let mut new_data = Vec::new();
149143
let mut current_long: i64 = 0;
150144
let mut bit_position = 0;
151145

152146
for &value in &normalised_ints {
153-
current_long |= (value as i64) << bit_position;
154-
bit_position += new_bit_size;
155-
156-
if bit_position >= 64 {
147+
if bit_position + new_bit_size > 64 {
157148
new_data.push(current_long);
158-
current_long = (value as i64) >> (new_bit_size - (bit_position - 64));
159-
bit_position -= 64;
149+
current_long = 0;
150+
bit_position = 0;
160151
}
152+
153+
current_long |= (value as i64) << bit_position;
154+
bit_position += new_bit_size;
161155
}
162156

163-
// Push any remaining bits in the final long
157+
// just in case there's remaining data to push, and avoid pushing empty longs
164158
if bit_position > 0 {
165159
new_data.push(current_long);
166160
}
167161

168-
// Verify the size of the new data matches expectations
169-
let expected_size = (4096 * new_bit_size).div_ceil(64);
170-
if new_data.len() != expected_size {
162+
// validation of size
163+
// the original logic is not suitable here
164+
// fuck it, each long waste is uncertain
165+
if new_data.is_empty() {
166+
return Err(WorldError::InvalidBlockStateData(
167+
"Resizing resulted in empty data".to_string(),
168+
));
169+
}
170+
171+
let blocks_per_long = 64 / new_bit_size;
172+
let expected_longs = 4096usize.div_ceil(blocks_per_long);
173+
if new_data.len() != expected_longs {
171174
return Err(WorldError::InvalidBlockStateData(format!(
172175
"Expected packed data size of {}, but got {}",
173-
expected_size,
176+
expected_longs,
174177
new_data.len()
175178
)));
176179
}
177-
// Update the chunk with the new packed data and a bit size
180+
178181
self.block_data = PaletteType::Indirect {
179182
bits_per_block: new_bit_size as u8,
180183
data: new_data,
@@ -218,26 +221,24 @@ impl Chunk {
218221
z: i32,
219222
block: BlockStateId,
220223
) -> Result<(), WorldError> {
221-
// Get old block
222224
let old_block = self.get_block(x, y, z)?;
223225
if old_block == block {
224-
// debug!("Block is the same as the old block");
225226
return Ok(());
226227
}
227-
// Get section
228+
228229
let section = self
229230
.sections
230231
.iter_mut()
231232
.find(|section| section.y == (y >> 4) as i8)
232233
.ok_or(WorldError::SectionOutOfBounds(y >> 4))?;
233234

235+
// from single palette to indirect palette if needed
234236
let mut converted = false;
235237
let mut new_contents = PaletteType::Indirect {
236238
bits_per_block: 4,
237239
data: vec![],
238240
palette: vec![],
239241
};
240-
241242
if let PaletteType::Single(val) = &section.block_states.block_data {
242243
new_contents = PaletteType::Indirect {
243244
bits_per_block: 4,
@@ -246,22 +247,22 @@ impl Chunk {
246247
};
247248
converted = true;
248249
}
249-
250250
if converted {
251251
section.block_states.block_data = new_contents;
252252
}
253253

254-
// Do different things based on the palette type
255-
match &mut section.block_states.block_data {
254+
let (block_palette_index, needs_resize, target_bits) = match &mut section
255+
.block_states
256+
.block_data
257+
{
256258
PaletteType::Single(_val) => {
257259
panic!("Single palette type should have been converted to indirect palette type");
258260
}
259261
PaletteType::Indirect {
260262
bits_per_block,
261-
data,
262263
palette,
264+
..
263265
} => {
264-
// debug!("Indirect mode");
265266
match section.block_states.block_counts.entry(old_block) {
266267
Entry::Occupied(mut occ_entry) => {
267268
let count = occ_entry.get_mut();
@@ -289,38 +290,58 @@ impl Chunk {
289290
empty_entry.insert(0);
290291
}
291292
}
292-
// Add new block
293+
294+
// Add new block to counts
293295
if let Some(e) = section.block_states.block_counts.get(&block) {
294296
section.block_states.block_counts.insert(block, e + 1);
295297
} else {
296-
// debug!("Adding block to block counts");
297298
section.block_states.block_counts.insert(block, 1);
298299
}
299-
// let required_bits = max((palette.len() as f32).log2().ceil() as u8, 4);
300-
// if *bits_per_block != required_bits {
301-
// section.block_states.resize(required_bits as usize)?;
302-
// }
303-
// Get block index
304-
let block_palette_index = palette
300+
301+
// find in palette
302+
let index = palette
305303
.iter()
306304
.position(|p| *p == block.to_varint())
307305
.unwrap_or_else(|| {
308-
// Add block to palette if it doesn't exist
309-
let index = palette.len() as i16;
306+
let idx = palette.len();
310307
palette.push(block.to_varint());
311-
index as usize
308+
idx
312309
});
313-
// Set block
310+
311+
// judge if we need to resize
312+
let required_bits = ((palette.len() as f32).log2().ceil() as u8).clamp(4, 15); // 15 is max in minecraft protocol
313+
314+
let resize = required_bits > *bits_per_block;
315+
316+
(index, resize, required_bits)
317+
}
318+
PaletteType::Direct { .. } => todo!("Implement direct palette for set_block"),
319+
};
320+
321+
if needs_resize {
322+
// debug!("Resizing section block states to {} bits", target_bits);
323+
section.block_states.resize(target_bits as usize)?;
324+
}
325+
326+
match &mut section.block_states.block_data {
327+
PaletteType::Indirect {
328+
bits_per_block,
329+
data,
330+
..
331+
} => {
314332
let blocks_per_i64 = (64f64 / *bits_per_block as f64).floor() as usize;
315333
let index =
316334
((y.abs() & 0xf) * 256 + (z.abs() & 0xf) * 16 + (x.abs() & 0xf)) as usize;
317335
let i64_index = index / blocks_per_i64;
336+
318337
let packed_u64 =
319338
data.get_mut(i64_index)
320339
.ok_or(WorldError::InvalidBlockStateData(format!(
321340
"Invalid block state data at index {i64_index}"
322341
)))?;
342+
323343
let offset = (index % blocks_per_i64) * *bits_per_block as usize;
344+
324345
if let Err(e) = ferrumc_general_purpose::data_packing::u32::write_nbit_u32(
325346
packed_u64,
326347
offset as u32,
@@ -332,25 +353,23 @@ impl Chunk {
332353
)));
333354
}
334355
}
335-
PaletteType::Direct { .. } => {
336-
todo!("Implement direct palette for set_block");
356+
_ => {
357+
return Err(WorldError::InvalidBlockStateData(
358+
"Unexpected palette type".to_string(),
359+
))
337360
}
338361
}
339362

340363
section.block_states.non_air_blocks = section
341364
.block_states
342365
.block_counts
343366
.iter()
344-
.filter(|(block, _)| {
345-
// Air, void air and cave air respectively
346-
![0, 12958, 12959].contains(&block.0)
347-
})
367+
.filter(|(block, _)| ![0, 12958, 12959].contains(&block.0))
348368
.map(|(_, count)| *count as u16)
349369
.sum();
350370

351-
self.sections
352-
.iter_mut()
353-
.for_each(|section| section.optimise().unwrap());
371+
section.optimise()?;
372+
354373
Ok(())
355374
}
356375

0 commit comments

Comments
 (0)