2023-12-28 12:20:38 +01:00
import os
import re
import subprocess
2024-03-07 13:19:44 +01:00
via_nbt_version = ' 4.4.0 '
2023-12-28 12:20:38 +01:00
2023-12-30 18:48:13 +01:00
# All of this would work better with bytecode rewriting, but here we go
2023-12-28 12:20:38 +01:00
replacements = {
# Gradle build script changes (less chance of running into conflicts by putting it here instead of the patch)
' name = " lenni0451 " ' : ' name = " viaversion " ' ,
' url = " https://maven.lenni0451.net/everything " ' : ' url = " https://repo.viaversion.com/ " ' ,
' api project( " :MCStructs-nbt " ) ' : f ' api " com.viaversion:nbt: { via_nbt_version } " ' ,
' maven_group=net.lenni0451.mcstructs ' : ' maven_group=com.viaversion.mcstructs ' ,
# Code changes
' import net.lenni0451.mcstructs.nbt.tags. ' : ' import com.github.steveice10.opennbt.tag.builtin. ' ,
' import net.lenni0451.mcstructs.nbt. ' : ' import com.github.steveice10.opennbt.tag.builtin. ' ,
' INbtTag ' : ' Tag ' ,
' INbtNumber ' : ' NumberTag ' ,
' tag.getNbtType() ' : ' tag ' ,
' ArrayTag.getLength() ' : ' ArrayTag.length() ' ,
' ArrayTag.get( ' : ' ArrayTag.getValue( ' ,
' type.name() ' : ' type.getClass().getSimpleName() ' ,
' NbtType ' : ' Tag ' ,
' import com.github.steveice10.opennbt.tag.builtin.Tag ' : ' import com.github.steveice10.opennbt.tag.builtin.* ' ,
' import com.github.steveice10.opennbt.tag.builtin.CompoundTag ' : ' import com.github.steveice10.opennbt.tag.builtin.* ' ,
# A special one
' if (!list.canAdd(tag)) throw new SNbtDeserializeException( " Unable to insert " + tag.getClass().getSimpleName() + " into ListTag of type " + list.getType().name()); ' : ' '
}
# Apply these separately to avoid changing json calls
extra_replacements = {
' .addString( ' : ' .putString( ' ,
' .addBoolean( ' : ' .putBoolean( ' ,
' .addByte( ' : ' .putByte( ' ,
' .addLong( ' : ' .putLong( ' ,
' .addInt( ' : ' .putInt( ' ,
' .addDouble( ' : ' .putDouble( ' ,
' .addShort( ' : ' .putShort( ' ,
' .addCompound( ' : ' .put( ' ,
' .addAll( ' : ' .putAll( ' ,
' .getTag().name() ' : ' .getClass() ' ,
' tag.name() ' : ' tag.getClass() ' ,
' list.getType().isNumber() ' : ' list.getElementType().isAssignableFrom(com.github.steveice10.opennbt.tag.builtin.NumberTag.class) ' ,
' this.styleSerializer.serialize(object.getStyle()).asCompoundTag() ' : ' (CompoundTag) this.styleSerializer.serialize(object.getStyle()) ' ,
' compound.add( ' : ' compound.put( ' ,
' .numberValue() ' : ' .getValue() ' ,
' (ACTION) ' : ' ( " action " ) ' ,
' (CONTENTS, ' : ' ( " contents " , ' ,
' (CONTENTS) ' : ' ( " contents " ) ' ,
' tags.get(0).getTag() ' : ' tags.get(0) ' ,
# Special ones
' !type.equals(tags.get(i).getTag()) ' : " type.getClass() != tags.get(i).getClass() " ,
' String type = ((StringTag) tag.get( " type " )).getValue() ' : ' String type = tag.contains( " type " ) ? ((StringTag) tag.get( " type " )).getValue() : null '
}
def main ( ) :
os . chdir ( ' MCStructs ' )
handle_file ( ' build.gradle ' )
handle_file ( ' gradle.properties ' )
deep ( ' MCStructs-snbt ' )
deep ( ' MCStructs-text ' )
# Apply additional manual changes
apply_patch ( ' ../patch.patch ' )
def apply_patch ( patch_file ) :
try :
subprocess . run ( [ ' git ' , ' apply ' , ' --reject ' , ' --ignore-whitespace ' , ' --ignore-space-change ' , patch_file ] ,
check = True )
print ( ' Applied patch ' )
except subprocess . CalledProcessError as e :
print ( f ' Error applying patch: { e } ' )
def deep ( path ) :
for p in os . listdir ( path ) :
p = os . path . join ( path , p )
if os . path . isdir ( p ) :
deep ( p )
elif p . endswith ( " .java " ) or p . endswith ( " .gradle " ) :
handle_file ( p )
2023-12-30 18:48:13 +01:00
def to_camel_case ( s ) :
words = s . split ( ' _ ' )
camel_case_words = [ words [ 0 ] . capitalize ( ) ] + [ word . capitalize ( ) for word in words [ 1 : ] ]
return ' ' . join ( camel_case_words )
2023-12-28 12:20:38 +01:00
def replace_get_value ( content , obj ) :
# First apply nullable replacements with the generic replacement, THEN the rest
content = replace_nullable_get ( obj , ' Compound ' , ' ' , ' null ' , content )
content = replace_nullable_get ( obj , ' List ' , ' ' , ' null ' , content )
content = re . sub ( fr ' { obj } \ .get( \ w+) \ (([^,)]+), null \ ) ' ,
fr ' ( { obj } .get( \ 2) instanceof \ 1Tag ? (( \ 1Tag) { obj } .get( \ 2)).getValue() : null) ' ,
content )
content = replace_nonnull_get ( obj , ' Compound ' , ' ' , ' new CompoundTag() ' , content )
2024-03-07 13:19:44 +01:00
content = replace_nonnull_get ( obj , ' List ' , ' ' , ' new ListTag<>() ' , content )
2023-12-28 12:20:38 +01:00
content = replace_nonnull_get ( obj , ' String ' , ' .getValue() ' , ' " " ' , content )
content = replace_nonnull_get ( obj , ' ByteArray ' , ' .getValue() ' , ' new byte[0] ' , content )
content = replace_nonnull_get ( obj , ' IntArray ' , ' .getValue() ' , ' new int[0] ' , content )
content = replace_nonnull_get ( obj , ' LongArray ' , ' .getValue() ' , ' new long[0] ' , content )
# Booleans are stored as byte tags
2023-12-30 18:48:13 +01:00
content = re . sub ( fr ' { obj } \ .getBoolean \ (([^)]+) \ ) ' ,
2023-12-28 12:20:38 +01:00
fr ' ( { obj } .get( \ 1) instanceof ByteTag ? ((ByteTag) { obj } .get( \ 1)).asBoolean() : false) ' ,
content )
2023-12-30 18:48:13 +01:00
# something.get(y).asXTag() -> ((XTag) something.get(y))
content = re . sub ( fr ' { obj } \ .get \ (([^)]+) \ ).as( \ w+Tag) \ ( \ ) ' ,
fr ' (( \ 2) { obj } .get( \ 1)) ' ,
content )
2023-12-28 12:20:38 +01:00
numeric_types = { ' Byte ' , ' Short ' , ' Int ' , ' Long ' , ' Float ' , ' Double ' }
for numeric_type in numeric_types :
content = replace_nonnull_get ( obj , numeric_type , f ' .as { numeric_type } () ' , ' 0 ' , content )
return content
def replace_nullable_get ( obj , tag_type , value_method , default , content ) :
pattern = re . compile ( fr ' { obj } \ .get { tag_type } \ (([^,)]+), null \ ) ' )
replacement = fr ' ( { obj } .get( \ 1) instanceof { tag_type } Tag ? (( { tag_type } Tag) { obj } .get( \ 1)) { value_method } : { default } ) '
return pattern . sub ( replacement , content )
def replace_nonnull_get ( obj , tag_type , value_method , default , content ) :
pattern = re . compile ( fr ' { obj } \ .get { tag_type } \ (([^)]+) \ ) ' )
replacement = fr ' ( { obj } .get( \ 1) instanceof { tag_type } Tag ? (( { tag_type } Tag) { obj } .get( \ 1)) { value_method } : { default } ) '
return pattern . sub ( replacement , content )
def handle_file ( path ) :
with open ( path , ' r ' ) as file :
content = file . read ( )
changed_content = content
for old , new in replacements . items ( ) :
changed_content = changed_content . replace ( old , new )
name = os . path . basename ( path )
if ' CompoundTag ' in changed_content :
# Some are very explicit, so that they don't break random code elsewhere/make it look like it worked fine
for old , new in extra_replacements . items ( ) :
changed_content = changed_content . replace ( old , new )
if ' JsonHoverEventSerializer ' not in name :
changed_content = changed_content . replace ( ' .add( " ' , ' .put( " ' )
# tag.isXTag() -> (tag instanceof XTag)
changed_content = re . sub ( r ' ( \ w+) \ .is( \ w+Tag) \ ( \ ) ' , r ' ( \ 1 instanceof \ 2) ' , changed_content )
# asXTag() -> cast
changed_content = re . sub ( r ' ( \ w+) \ .as( \ w+Tag) \ ( \ ) ' , r ' (( \ 2) \ 1) ' , changed_content )
changed_content = re . sub ( r ' tags.get \ (i \ ) \ .as( \ w+Tag) \ ( \ ) ' , r ' (( \ 1) tags.get(i)) ' , changed_content )
# tag.contains(s, Tag.X) -> tag.get(s) instanceof XTag
changed_content = re . sub ( r ' ( \ w+) \ .contains \ (([^,)]+), Tag \ .( \ w+) \ ) ' ,
2023-12-30 18:48:13 +01:00
lambda m : f ' ( { m . group ( 1 ) } .get( { m . group ( 2 ) } ) instanceof { to_camel_case ( m . group ( 3 ) ) } Tag) ' ,
2023-12-28 12:20:38 +01:00
changed_content )
changed_content = re . sub ( r ' \ .contains \ (([^,)]+), Tag \ .( \ w+) \ ) ' ,
2023-12-30 18:48:13 +01:00
lambda m : f ' .get( { m . group ( 1 ) } ) instanceof { to_camel_case ( m . group ( 2 ) ) } Tag ' ,
2023-12-28 12:20:38 +01:00
changed_content )
# Tag.X.equals(tagType) -> tagType instance XTag
changed_content = re . sub ( r ' Tag \ .( \ w+).equals \ (( \ w+) \ ) ' ,
2023-12-30 18:48:13 +01:00
lambda m : f ' ( { m . group ( 2 ) } instanceof { to_camel_case ( m . group ( 1 ) ) } Tag) ' ,
2023-12-28 12:20:38 +01:00
changed_content )
# tag.getX -> cast to tag with default value
changed_content = replace_get_value ( changed_content , ' tag ' )
changed_content = replace_get_value ( changed_content , ' rawTag ' )
changed_content = replace_get_value ( changed_content , ' rawEntity ' )
changed_content = replace_get_value ( changed_content , ' score ' )
changed_content = replace_get_value ( changed_content , ' clickEvent ' )
changed_content = replace_get_value ( changed_content , ' parsed ' )
if content == changed_content :
return
with open ( path , ' w ' ) as file :
file . write ( changed_content )
print ( ' Wrote to ' , path )
if __name__ == ' __main__ ' :
main ( )