22
33import static net .minecraft .nbt .NbtIo .writeCompressed ;
44
5+ import java .io .BufferedInputStream ;
56import java .io .ByteArrayInputStream ;
67import java .io .ByteArrayOutputStream ;
8+ import java .io .DataInputStream ;
79import java .io .File ;
810import java .io .IOException ;
11+ import java .io .InputStream ;
912import java .nio .ByteBuffer ;
1013import java .util .Iterator ;
1114import java .util .Map ;
1518import java .util .concurrent .atomic .AtomicBoolean ;
1619import java .util .function .Function ;
1720import java .util .function .Supplier ;
21+ import java .util .zip .GZIPInputStream ;
1822
1923import javax .annotation .Nonnull ;
2024import javax .annotation .Nullable ;
2125
2226import com .google .common .collect .Maps ;
27+ import com .mojang .datafixers .DataFixer ;
2328import com .mojang .datafixers .util .Either ;
2429import cubicchunks .regionlib .impl .EntryLocation2D ;
2530import cubicchunks .regionlib .impl .EntryLocation3D ;
2631import cubicchunks .regionlib .impl .SaveCubeColumns ;
2732import io .github .opencubicchunks .cubicchunks .CubicChunks ;
2833import io .github .opencubicchunks .cubicchunks .chunk .util .CubePos ;
34+ import io .github .opencubicchunks .cubicchunks .utils .Coords ;
35+ import net .minecraft .SharedConstants ;
2936import net .minecraft .Util ;
3037import net .minecraft .nbt .CompoundTag ;
38+ import net .minecraft .nbt .ListTag ;
39+ import net .minecraft .nbt .NbtAccounter ;
3140import net .minecraft .nbt .NbtIo ;
41+ import net .minecraft .nbt .NbtUtils ;
42+ import net .minecraft .nbt .Tag ;
43+ import net .minecraft .resources .ResourceKey ;
44+ import net .minecraft .util .datafix .DataFixTypes ;
3245import net .minecraft .util .thread .ProcessorMailbox ;
3346import net .minecraft .util .thread .StrictQueue ;
3447import net .minecraft .world .level .ChunkPos ;
48+ import net .minecraft .world .level .Level ;
49+ import net .minecraft .world .level .chunk .ChunkStatus ;
50+ import net .minecraft .world .level .chunk .storage .ChunkStorage ;
51+ import net .minecraft .world .level .storage .DimensionDataStorage ;
3552import org .apache .logging .log4j .Logger ;
3653
3754public class RegionCubeIO {
@@ -46,13 +63,15 @@ public class RegionCubeIO {
4663 private final Map <ChunkPos , SaveEntry > pendingChunkWrites = Maps .newLinkedHashMap ();
4764 private final Map <CubePos , SaveEntry > pendingCubeWrites = Maps .newLinkedHashMap ();
4865
66+ private final DataFixer fixerUpper ;
4967 private final ProcessorMailbox <StrictQueue .IntRunnable > chunkExecutor ;
5068 private final ProcessorMailbox <StrictQueue .IntRunnable > cubeExecutor ;
5169
5270 private final AtomicBoolean shutdownRequested = new AtomicBoolean ();
5371
54- public RegionCubeIO (File storageFolder , @ Nullable String chunkWorkerName , String cubeWorkerName ) throws IOException {
72+ public RegionCubeIO (File storageFolder , DataFixer dataFixer , @ Nullable String chunkWorkerName , String cubeWorkerName ) throws IOException {
5573 this .storageFolder = storageFolder ;
74+ this .fixerUpper = dataFixer ;
5675
5776
5877 this .chunkExecutor = new ProcessorMailbox <>(new StrictQueue .FixedPriorityQueue (Priority .values ().length ), Util .ioPool (), "RegionCubeIO-" + chunkWorkerName );
@@ -118,6 +137,22 @@ public CompletableFuture<Void> saveCubeNBT(CubePos cubePos, CompoundTag cubeNBT)
118137 return Either .left (null );
119138 }
120139
140+ boolean isOldData = false ;
141+ try (GZIPInputStream in = new GZIPInputStream (new ByteArrayInputStream (buf .get ().array ()))) {
142+ // NBT can't begin with byte 255, so this acts as a marker for
143+ // 1.12.2 "proto-big-cube" converted data
144+ if (in .read () == 255 ) {
145+ // a second byte with value 0 for potential future extension
146+ if (in .read () != 0 ) {
147+ throw new RuntimeException ("Invalid data, expected 0" );
148+ }
149+ isOldData = true ;
150+ }
151+ }
152+ if (isOldData ) {
153+ return Either .left (loadOldNbt (cubePos , buf .get ().array ()));
154+ }
155+
121156 CompoundTag compoundnbt = NbtIo .readCompressed (new ByteArrayInputStream (buf .get ().array ()));
122157 return Either .left (compoundnbt );
123158 } catch (Exception exception ) {
@@ -138,6 +173,121 @@ public CompletableFuture<Void> saveCubeNBT(CubePos cubePos, CompoundTag cubeNBT)
138173 }
139174 }
140175
176+ @ Nullable
177+ private CompoundTag loadOldNbt (CubePos pos , byte [] data ) throws IOException {
178+ ByteArrayInputStream in = new ByteArrayInputStream (data );
179+ CompoundTag [] cubeTags = new CompoundTag [8 ];
180+ try (InputStream gzip = new BufferedInputStream (new GZIPInputStream (in ))) {
181+ // skip byte 255 and byte 0
182+ gzip .read ();
183+ gzip .read ();
184+ for (int i = 0 ; i < 8 ; i ++) {
185+ cubeTags [i ] = NbtIo .read (new DataInputStream (gzip ), NbtAccounter .UNLIMITED );
186+ }
187+ }
188+
189+ return mergeOldNbt (pos , cubeTags );
190+ }
191+
192+ @ Nullable
193+ private CompoundTag mergeOldNbt (CubePos pos , CompoundTag [] cubeTags ) {
194+ CompoundTag outTag = new CompoundTag ();
195+ for (int i = 0 ; i < cubeTags .length ; i ++) {
196+ CompoundTag level = cubeTags [i ].getCompound ("Level" );
197+ level .put ("TerrainPopulated" , level .get ("populated" ));
198+ level .put ("LightPopulated" , level .get ("initLightDone" ));
199+ ListTag tileTicks = level .getList ("TileTicks" , CompoundTag .TAG_COMPOUND );
200+
201+ // prepare tile ticks to that high bits of "y" coordinate are actually cube section index
202+ for (Tag tileTick : tileTicks ) {
203+ CompoundTag tick = (CompoundTag ) tileTick ;
204+ int x = tick .getInt ("x" );
205+ int y = tick .getInt ("y" );
206+ int z = tick .getInt ("z" );
207+ int idx = Coords .blockToIndex (x , y , z );
208+ tick .putInt ("y" , y & 0xF | idx << 4 );
209+ }
210+
211+ int version = ChunkStorage .getVersion (cubeTags [i ]);
212+
213+ cubeTags [i ] = NbtUtils .update (this .fixerUpper , DataFixTypes .CHUNK , cubeTags [i ], version , 1493 );
214+ //if (nbt.getCompound("Level").getBoolean("hasLegacyStructureData")) {
215+ // if (this.legacyStructureHandler == null) {
216+ // this.legacyStructureHandler = LegacyStructureDataHandler.getLegacyStructureHandler(worldKey, (DimensionDataStorage)persistentStateManagerFactory.get());
217+ // }
218+ // nbt = this.legacyStructureHandler.updateFromLegacy(nbt);
219+ //}
220+ cubeTags [i ] = NbtUtils .update (this .fixerUpper , DataFixTypes .CHUNK , cubeTags [i ], Math .max (1493 , version ));
221+ if (cubeTags [i ] == null ) {
222+ LOGGER .warn ("Dropping incomplete cube at " + pos );
223+ return null ;
224+ }
225+ }
226+ CompoundTag levelOut = new CompoundTag ();
227+ levelOut .putInt ("xPos" , pos .getX ());
228+ levelOut .putInt ("yPos" , pos .getY ());
229+ levelOut .putInt ("zPos" , pos .getZ ());
230+
231+ outTag .put ("ToBeTicked" , new ListTag ());
232+ outTag .put ("Level" , levelOut );
233+ // TODO: biomes
234+ outTag .putIntArray ("Biomes" , new int [8 * 8 * 8 ]);
235+ for (int i = 0 ; i < cubeTags .length ; i ++) {
236+ CompoundTag cube = cubeTags [i ];
237+ CompoundTag level = cube .getCompound ("Level" );
238+ ListTag toBeTicked = cube .getList ("ToBeTicked" , Tag .TAG_LIST );
239+ ListTag outToBeTicked = outTag .getList ("ToBeTicked" , Tag .TAG_LIST );
240+ for (int i1 = 0 ; i1 < toBeTicked .size (); i1 ++) {
241+ ListTag toTickEntry = toBeTicked .getList (i1 );
242+ ListTag toTickEntryOut = outToBeTicked .getList (i1 );
243+ toTickEntryOut .addAll (toTickEntry );
244+ outToBeTicked .set (i1 , toTickEntryOut );
245+ }
246+
247+ level .putLong ("LastUpdate" , Math .max (level .getLong ("LastUpdate" ), levelOut .getLong ("LastUpdate" )));
248+ level .putLong ("InhabitedTime" , Math .max (level .getLong ("InhabitedTime" ), levelOut .getLong ("InhabitedTime" )));
249+
250+ ChunkStatus status = ChunkStatus .byName (level .getString ("Status" ));
251+ ChunkStatus oldStatus = levelOut .contains ("Status" ) ? ChunkStatus .byName (level .getString ("Status" )) : null ;
252+ ChunkStatus newStatus = oldStatus == null ? status : oldStatus .isOrAfter (ChunkStatus .SPAWN ) ? status : ChunkStatus .EMPTY ;
253+ levelOut .putString ("Status" , newStatus .getName ());
254+
255+ ListTag sections = levelOut .getList ("Sections" , Tag .TAG_COMPOUND );
256+ levelOut .put ("Sections" , sections );
257+ CompoundTag section = level .getList ("Sections" , Tag .TAG_COMPOUND ).getCompound (0 );
258+ section .putShort ("i" , (short ) i );
259+ sections .add (section );
260+
261+ levelOut .putBoolean ("isLightOn" , true );
262+
263+ ListTag tileEntities = levelOut .getList ("TileEntities" , Tag .TAG_COMPOUND );
264+ levelOut .put ("TileEntities" , tileEntities );
265+ ListTag tileEntitiesOld = level .getList ("TileEntities" , Tag .TAG_COMPOUND );
266+ tileEntities .addAll (tileEntitiesOld );
267+
268+ ListTag entities = levelOut .getList ("Entities" , Tag .TAG_COMPOUND );
269+ levelOut .put ("Entities" , entities );
270+ ListTag entitiesOld = level .getList ("Entities" , Tag .TAG_COMPOUND );
271+ entities .addAll (entitiesOld );
272+
273+ }
274+ return outTag ;
275+ }
276+
277+ public CompoundTag upgradeChunkTag (ResourceKey <Level > worldKey , Supplier <DimensionDataStorage > persistentStateManagerFactory , CompoundTag nbt ) {
278+ int i = ChunkStorage .getVersion (nbt );
279+ if (i < 1493 ) {
280+ throw new IllegalArgumentException ("Pre-1.17 version handled elsewhere, but trying " + i );
281+ }
282+
283+ nbt = NbtUtils .update (this .fixerUpper , DataFixTypes .CHUNK , nbt , Math .max (1493 , i ));
284+ if (i < SharedConstants .getCurrentVersion ().getWorldVersion ()) {
285+ nbt .putInt ("DataVersion" , SharedConstants .getCurrentVersion ().getWorldVersion ());
286+ }
287+
288+ return nbt ;
289+ }
290+
141291 public CompletableFuture <Void > saveChunkNBT (ChunkPos chunkPos , CompoundTag cubeNBT ) {
142292 return this .submitChunkTask (() -> {
143293 SaveEntry entry = this .pendingChunkWrites .computeIfAbsent (chunkPos , (pos ) -> new SaveEntry (cubeNBT ));
0 commit comments