> */ function loadUpgradeTable(string $file, bool $reverse) : array{ try{ $contents = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => file_get_contents($file)); }catch(\ErrorException $e){ throw new \RuntimeException("Failed loading mapping table file $file: " . $e->getMessage(), 0, $e); } $data = (new NetworkNbtSerializer())->readMultiple($contents); $result = []; for($i = 0; isset($data[$i]); $i += 2){ $oldTag = $data[$i]->mustGetCompoundTag(); $newTag = $data[$i + 1]->mustGetCompoundTag(); $old = BlockStateData::fromNbt($reverse ? $newTag : $oldTag); $new = BlockStateData::fromNbt($reverse ? $oldTag : $newTag); $result[$old->getName()][] = new BlockStateMapping( $old, $new ); } return $result; } /** * @param true[] $removedPropertiesCache * @param Tag[][] $remappedPropertyValuesCache * @phpstan-param array $removedPropertiesCache * @phpstan-param array> $remappedPropertyValuesCache */ function processState(BlockStateData $old, BlockStateData $new, BlockStateUpgradeSchema $result, array &$removedPropertiesCache, array &$remappedPropertyValuesCache) : void{ //new and old IDs are the same; compare states $oldName = $old->getName(); $oldStates = $old->getStates(); $newStates = $new->getStates(); $propertyRemoved = []; $propertyAdded = []; foreach(Utils::stringifyKeys($oldStates) as $propertyName => $oldProperty){ $newProperty = $new->getState($propertyName); if($newProperty === null){ $propertyRemoved[$propertyName] = $oldProperty; }elseif(!$newProperty->equals($oldProperty)){ if(!isset($remappedPropertyValuesCache[$propertyName][$oldProperty->getValue()])){ $result->remappedPropertyValues[$oldName][$propertyName][] = new BlockStateUpgradeSchemaValueRemap( $oldProperty, $newProperty ); $remappedPropertyValuesCache[$propertyName][$oldProperty->getValue()] = $newProperty; } } } foreach(Utils::stringifyKeys($newStates) as $propertyName => $value){ if($old->getState($propertyName) === null){ $propertyAdded[$propertyName] = $value; } } if(count($propertyAdded) === 0 && count($propertyRemoved) === 0){ return; } if(count($propertyAdded) === 1 && count($propertyRemoved) === 1){ $propertyOldName = array_key_first($propertyRemoved); $propertyNewName = array_key_first($propertyAdded); $propertyOldValue = $propertyRemoved[$propertyOldName]; $propertyNewValue = $propertyAdded[$propertyNewName]; $existingPropertyValueMap = $remappedPropertyValuesCache[$propertyOldName][$propertyOldValue->getValue()] ?? null; if($propertyOldName !== $propertyNewName){ if(!$propertyOldValue->equals($propertyNewValue) && $existingPropertyValueMap === null){ \GlobalLogger::get()->warning("warning: guessing that $oldName has $propertyOldName renamed to $propertyNewName with a value map of $propertyOldValue mapped to $propertyNewValue");; } //this is a guess; it might not be reliable if the value changed as well //this will probably never be an issue, but it might rear its ugly head in the future $result->renamedProperties[$oldName][$propertyOldName] = $propertyNewName; } if(!$propertyOldValue->equals($propertyNewValue)){ $mapped = true; if($existingPropertyValueMap !== null && !$existingPropertyValueMap->equals($propertyNewValue)){ if($existingPropertyValueMap->equals($propertyOldValue)){ \GlobalLogger::get()->warning("warning: guessing that the value $propertyOldValue of $propertyNewValue did not change");; $mapped = false; }else{ \GlobalLogger::get()->warning("warning: mismatch of new value for $propertyNewName for $oldName: $propertyOldValue seen mapped to $propertyNewValue and $existingPropertyValueMap");; } } if($mapped && !isset($remappedPropertyValuesCache[$propertyOldName][$propertyOldValue->getValue()])){ //value remap $result->remappedPropertyValues[$oldName][$propertyOldName][] = new BlockStateUpgradeSchemaValueRemap( $propertyRemoved[$propertyOldName], $propertyAdded[$propertyNewName] ); $remappedPropertyValuesCache[$propertyOldName][$propertyOldValue->getValue()] = $propertyNewValue; } }elseif($existingPropertyValueMap !== null){ \GlobalLogger::get()->warning("warning: multiple values found for value $propertyOldValue of $propertyNewName on block $oldName, guessing it did not change");; $remappedPropertyValuesCache[$propertyOldName][$propertyOldValue->getValue()] = $propertyNewValue; } }else{ if(count($propertyAdded) !== 0 && count($propertyRemoved) === 0){ foreach(Utils::stringifyKeys($propertyAdded) as $propertyAddedName => $propertyAddedValue){ $existingDefault = $result->addedProperties[$oldName][$propertyAddedName] ?? null; if($existingDefault !== null && !$existingDefault->equals($propertyAddedValue)){ throw new \UnexpectedValueException("Ambiguous default value for added property $propertyAddedName on block $oldName"); } $result->addedProperties[$oldName][$propertyAddedName] = $propertyAddedValue; } }elseif(count($propertyRemoved) !== 0 && count($propertyAdded) === 0){ foreach(Utils::stringifyKeys($propertyRemoved) as $propertyRemovedName => $propertyRemovedValue){ if(!isset($removedPropertiesCache[$propertyRemovedName])){ //to avoid having useless keys in the output $result->removedProperties[$oldName][] = $propertyRemovedName; $removedPropertiesCache[$propertyRemovedName] = $propertyRemovedName; } } }else{ $result->remappedStates[$oldName][] = new BlockStateUpgradeSchemaBlockRemap( $oldStates, $new->getName(), $newStates ); \GlobalLogger::get()->warning("warning: multiple properties added and removed for $oldName; added full state remap");; } } } /** * @param BlockStateMapping[][] $upgradeTable * @phpstan-param array> $upgradeTable */ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgradeSchema{ $foundVersion = -1; foreach(Utils::stringifyKeys($upgradeTable) as $blockStateMappings){ foreach($blockStateMappings as $mapping){ if($foundVersion === -1 || $mapping->new->getVersion() === $foundVersion){ $foundVersion = $mapping->new->getVersion(); }else{ throw new AssumptionFailedError("Mixed versions found"); } } } $result = new BlockStateUpgradeSchema( ($foundVersion >> 24) & 0xff, ($foundVersion >> 16) & 0xff, ($foundVersion >> 8) & 0xff, ($foundVersion & 0xff), 0 ); foreach(Utils::stringifyKeys($upgradeTable) as $oldName => $blockStateMappings){ $newNameFound = []; $removedPropertiesCache = []; $remappedPropertyValuesCache = []; foreach($blockStateMappings as $mapping){ $newName = $mapping->new->getName(); $newNameFound[$newName] = true; } if(count($newNameFound) === 1){ $newName = array_key_first($newNameFound); if($newName !== $oldName){ $result->renamedIds[$oldName] = array_key_first($newNameFound); } foreach($blockStateMappings as $mapping){ processState($mapping->old, $mapping->new, $result, $removedPropertiesCache, $remappedPropertyValuesCache); } }else{ //block mapped to multiple different new IDs; we can't guess these, so we just do a plain old remap foreach($blockStateMappings as $mapping){ if(!$mapping->old->equals($mapping->new)){ $result->remappedStates[$mapping->old->getName()][] = new BlockStateUpgradeSchemaBlockRemap( $mapping->old->getStates(), $mapping->new->getName(), $mapping->new->getStates() ); } } } } return $result; } /** * @param string[] $argv */ function main(array $argv) : int{ if(count($argv) !== 3){ fwrite(STDERR, "Required arguments: input file path, output file path\n"); return 1; } $input = $argv[1]; $output = $argv[2]; $table = loadUpgradeTable($input, false); ksort($table, SORT_STRING); $diff = generateBlockStateUpgradeSchema($table); if($diff->isEmpty()){ \GlobalLogger::get()->warning("All states appear to be the same! No schema generated."); return 0; } file_put_contents( $output, json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($diff), JSON_PRETTY_PRINT) . "\n" ); \GlobalLogger::get()->info("Schema file $output generated successfully."); return 0; } exit(main($argv));