I've been trying to solve this too, very crucial for my mod and can't be done with events (I don't want to use coremods either). Here's something I came up with, it uses nasty amount of reflection so it might and probably will break with updates. I have tested it a bit, and it seems to work fine, so let me know if you have any issues with it.
public class BlockReplaceHelper{
public static boolean replaceBlock(Block toReplace, Class<? extends Block> blockClass){
Field modifiersField=null;
try{
modifiersField=Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
for(Field field:Blocks.class.getDeclaredFields()){
if (Block.class.isAssignableFrom(field.getType())){
Block block=(Block)field.get(null);
if (block==toReplace){
String registryName=Block.blockRegistry.getNameForObject(block);
int id=Block.getIdFromBlock(block);
ItemBlock item=(ItemBlock)Item.getItemFromBlock(block);
System.out.println("Replacing block - "+id+"/"+registryName);
Block newBlock=blockClass.newInstance();
FMLControlledNamespacedRegistry<Block> registry=GameData.blockRegistry;
registry.putObject(registryName,newBlock);
Field map=RegistryNamespaced.class.getDeclaredFields()[0];
map.setAccessible(true);
((ObjectIntIdentityMap)map.get(registry)).func_148746_a(newBlock,id);
map=FMLControlledNamespacedRegistry.class.getDeclaredField("namedIds");
map.setAccessible(true);
((BiMap)map.get(registry)).put(registryName,id);
field.setAccessible(true);
int modifiers=modifiersField.getInt(field);
modifiers&=~Modifier.FINAL;
modifiersField.setInt(field,modifiers);
field.set(null,newBlock);
Field itemblock=ItemBlock.class.getDeclaredFields()[0];
itemblock.setAccessible(true);
modifiers=modifiersField.getInt(itemblock);
modifiers&=~Modifier.FINAL;
modifiersField.setInt(itemblock,modifiers);
itemblock.set(item,newBlock);
System.out.println("Check field: "+field.get(null).getClass());
System.out.println("Check registry: "+Block.blockRegistry.getObjectById(id).getClass());
System.out.println("Check item: "+((ItemBlock)Item.getItemFromBlock(newBlock)).field_150939_a.getClass());
}
}
}
}catch(Exception e){
e.printStackTrace();
return false;
}
return true;
}
}
Example usage (in preinit):
BlockReplaceHelper.replaceBlock(Blocks.dragon_egg,BlockDragonEggCustom.class);