Remove old paulscode libraries.
Re-work EnumHelper and ObjectHolder systems to bypass JITer of final fields. Other cleanups.
This commit is contained in:
parent
2798080f60
commit
12869710de
55 changed files with 495 additions and 5859 deletions
10
patches/minecraft/net/minecraft/init/Biomes.java.patch
Normal file
10
patches/minecraft/net/minecraft/init/Biomes.java.patch
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
--- a/net/minecraft/init/Biomes.java
|
||||||
|
+++ b/net/minecraft/init/Biomes.java
|
||||||
|
@@ -3,6 +3,7 @@
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import net.minecraft.world.biome.Biome;
|
||||||
|
|
||||||
|
+@net.minecraftforge.registries.ObjectHolder("minecraft")
|
||||||
|
public abstract class Biomes {
|
||||||
|
public static final Biome OCEAN;
|
||||||
|
public static final Biome DEFAULT;
|
10
patches/minecraft/net/minecraft/init/Blocks.java.patch
Normal file
10
patches/minecraft/net/minecraft/init/Blocks.java.patch
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
--- a/net/minecraft/init/Blocks.java
|
||||||
|
+++ b/net/minecraft/init/Blocks.java
|
||||||
|
@@ -5,6 +5,7 @@
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
|
+@net.minecraftforge.registries.ObjectHolder("minecraft")
|
||||||
|
public class Blocks {
|
||||||
|
private static final Set<Block> CACHE;
|
||||||
|
public static final Block AIR;
|
10
patches/minecraft/net/minecraft/init/Enchantments.java.patch
Normal file
10
patches/minecraft/net/minecraft/init/Enchantments.java.patch
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
--- a/net/minecraft/init/Enchantments.java
|
||||||
|
+++ b/net/minecraft/init/Enchantments.java
|
||||||
|
@@ -4,6 +4,7 @@
|
||||||
|
import net.minecraft.enchantment.Enchantment;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
|
+@net.minecraftforge.registries.ObjectHolder("minecraft")
|
||||||
|
public class Enchantments {
|
||||||
|
public static final Enchantment PROTECTION = getRegisteredEnchantment("protection");
|
||||||
|
public static final Enchantment FIRE_PROTECTION = getRegisteredEnchantment("fire_protection");
|
10
patches/minecraft/net/minecraft/init/Fluids.java.patch
Normal file
10
patches/minecraft/net/minecraft/init/Fluids.java.patch
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
--- a/net/minecraft/init/Fluids.java
|
||||||
|
+++ b/net/minecraft/init/Fluids.java
|
||||||
|
@@ -6,6 +6,7 @@
|
||||||
|
import net.minecraft.fluid.Fluid;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
|
+@net.minecraftforge.registries.ObjectHolder("minecraft")
|
||||||
|
public class Fluids {
|
||||||
|
private static final Set<Fluid> field_207214_f;
|
||||||
|
public static final Fluid field_204541_a;
|
10
patches/minecraft/net/minecraft/init/Items.java.patch
Normal file
10
patches/minecraft/net/minecraft/init/Items.java.patch
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
--- a/net/minecraft/init/Items.java
|
||||||
|
+++ b/net/minecraft/init/Items.java
|
||||||
|
@@ -3,6 +3,7 @@
|
||||||
|
import net.minecraft.item.Item;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
|
+@net.minecraftforge.registries.ObjectHolder("minecraft")
|
||||||
|
public class Items {
|
||||||
|
public static final Item AIR;
|
||||||
|
public static final Item IRON_SHOVEL;
|
10
patches/minecraft/net/minecraft/init/MobEffects.java.patch
Normal file
10
patches/minecraft/net/minecraft/init/MobEffects.java.patch
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
--- a/net/minecraft/init/MobEffects.java
|
||||||
|
+++ b/net/minecraft/init/MobEffects.java
|
||||||
|
@@ -4,6 +4,7 @@
|
||||||
|
import net.minecraft.potion.Potion;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
|
+@net.minecraftforge.registries.ObjectHolder("minecraft")
|
||||||
|
public class MobEffects {
|
||||||
|
public static final Potion SPEED;
|
||||||
|
public static final Potion SLOWNESS;
|
10
patches/minecraft/net/minecraft/init/Particles.java.patch
Normal file
10
patches/minecraft/net/minecraft/init/Particles.java.patch
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
--- a/net/minecraft/init/Particles.java
|
||||||
|
+++ b/net/minecraft/init/Particles.java
|
||||||
|
@@ -7,6 +7,7 @@
|
||||||
|
import net.minecraft.particles.RedstoneParticleData;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
|
+@net.minecraftforge.registries.ObjectHolder("minecraft")
|
||||||
|
public class Particles {
|
||||||
|
public static final BasicParticleType field_197608_a;
|
||||||
|
public static final BasicParticleType field_197609_b;
|
10
patches/minecraft/net/minecraft/init/PotionTypes.java.patch
Normal file
10
patches/minecraft/net/minecraft/init/PotionTypes.java.patch
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
--- a/net/minecraft/init/PotionTypes.java
|
||||||
|
+++ b/net/minecraft/init/PotionTypes.java
|
||||||
|
@@ -5,6 +5,7 @@
|
||||||
|
import net.minecraft.potion.PotionType;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
|
+@net.minecraftforge.registries.ObjectHolder("minecraft")
|
||||||
|
public class PotionTypes {
|
||||||
|
private static final Set<PotionType> CACHE;
|
||||||
|
public static final PotionType EMPTY;
|
10
patches/minecraft/net/minecraft/init/SoundEvents.java.patch
Normal file
10
patches/minecraft/net/minecraft/init/SoundEvents.java.patch
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
--- a/net/minecraft/init/SoundEvents.java
|
||||||
|
+++ b/net/minecraft/init/SoundEvents.java
|
||||||
|
@@ -3,6 +3,7 @@
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import net.minecraft.util.SoundEvent;
|
||||||
|
|
||||||
|
+@net.minecraftforge.registries.ObjectHolder("minecraft")
|
||||||
|
public class SoundEvents {
|
||||||
|
public static final SoundEvent AMBIENT_CAVE;
|
||||||
|
public static final SoundEvent field_204326_e;
|
|
@ -0,0 +1,11 @@
|
||||||
|
--- a/net/minecraft/item/ArmorMaterial.java
|
||||||
|
+++ b/net/minecraft/item/ArmorMaterial.java
|
||||||
|
@@ -77,4 +77,8 @@
|
||||||
|
public float func_200901_e() {
|
||||||
|
return this.toughness;
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ public static ArmorMaterial create(String name, String p_i48533_3_, int p_i48533_4_, int[] p_i48533_5_, int p_i48533_6_, SoundEvent p_i48533_7_, float p_i48533_8_, Supplier<Ingredient> p_i48533_9_) {
|
||||||
|
+ return null;
|
||||||
|
}
|
||||||
|
+}
|
|
@ -1,947 +0,0 @@
|
||||||
|
|
||||||
package ibxm;
|
|
||||||
|
|
||||||
public class Channel {
|
|
||||||
public int pattern_loop_row;
|
|
||||||
|
|
||||||
private Module module;
|
|
||||||
private Instrument instrument;
|
|
||||||
private Sample sample;
|
|
||||||
private int[] global_volume, current_note;
|
|
||||||
private boolean linear_periods, fast_volume_slides, key_on, silent;
|
|
||||||
private int sample_idx, sample_frac, step, left_gain, right_gain;
|
|
||||||
@SuppressWarnings("unused") //Forge
|
|
||||||
private int volume, panning, fine_tune, period, porta_period, key_add;
|
|
||||||
private int tremolo_speed, tremolo_depth, tremolo_tick, tremolo_wave, tremolo_add;
|
|
||||||
private int vibrato_speed, vibrato_depth, vibrato_tick, vibrato_wave, vibrato_add;
|
|
||||||
private int volume_slide_param, portamento_param, retrig_param;
|
|
||||||
private int volume_envelope_tick, panning_envelope_tick;
|
|
||||||
private int effect_tick, trigger_tick, fade_out_volume, random_seed;
|
|
||||||
|
|
||||||
private int log_2_sampling_rate;
|
|
||||||
private static final int LOG_2_29024 = LogTable.log_2( 29024 );
|
|
||||||
private static final int LOG_2_8287 = LogTable.log_2( 8287 );
|
|
||||||
private static final int LOG_2_8363 = LogTable.log_2( 8363 );
|
|
||||||
private static final int LOG_2_1712 = LogTable.log_2( 1712 );
|
|
||||||
|
|
||||||
private static final int[] sine_table = new int[] {
|
|
||||||
0, 24 , 49, 74, 97, 120, 141, 161, 180, 197, 212, 224, 235, 244, 250, 253,
|
|
||||||
255, 253, 250, 244, 235, 224, 212, 197, 180, 161, 141, 120, 97, 74, 49, 24
|
|
||||||
};
|
|
||||||
|
|
||||||
public Channel( Module mod, int sampling_rate, int[] global_vol ) {
|
|
||||||
module = mod;
|
|
||||||
global_volume = global_vol;
|
|
||||||
linear_periods = module.linear_periods;
|
|
||||||
fast_volume_slides = module.fast_volume_slides;
|
|
||||||
current_note = new int[ 5 ];
|
|
||||||
log_2_sampling_rate = LogTable.log_2( sampling_rate );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
|
||||||
tremolo_speed = 0;
|
|
||||||
tremolo_depth = 0;
|
|
||||||
tremolo_wave = 0;
|
|
||||||
vibrato_speed = 0;
|
|
||||||
vibrato_depth = 0;
|
|
||||||
vibrato_wave = 0;
|
|
||||||
volume_slide_param = 0;
|
|
||||||
portamento_param = 0;
|
|
||||||
retrig_param = 0;
|
|
||||||
random_seed = 0xABC123;
|
|
||||||
instrument = module.get_instrument( 0 );
|
|
||||||
row( 48, 256, 0, 0, 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resample( int[] mixing_buffer, int frame_offset, int frames, int quality ) {
|
|
||||||
if( !silent ) {
|
|
||||||
switch( quality ) {
|
|
||||||
default:
|
|
||||||
sample.resample_nearest( sample_idx, sample_frac, step, left_gain, right_gain, mixing_buffer, frame_offset, frames );
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
sample.resample_linear( sample_idx, sample_frac, step, left_gain, right_gain, mixing_buffer, frame_offset, frames );
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
sample.resample_sinc( sample_idx, sample_frac, step, left_gain, right_gain, mixing_buffer, frame_offset, frames );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update_sample_idx( int samples ) {
|
|
||||||
sample_frac += step * samples;
|
|
||||||
sample_idx += sample_frac >> IBXM.FP_SHIFT;
|
|
||||||
sample_frac &= IBXM.FP_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_volume( int vol ) {
|
|
||||||
if( vol < 0 ) {
|
|
||||||
vol = 0;
|
|
||||||
}
|
|
||||||
if( vol > 64 ) {
|
|
||||||
vol = 64;
|
|
||||||
}
|
|
||||||
volume = vol;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_panning( int pan ) {
|
|
||||||
if( pan < 0 ) {
|
|
||||||
pan = 0;
|
|
||||||
}
|
|
||||||
if( pan > 255 ) {
|
|
||||||
pan = 255;
|
|
||||||
}
|
|
||||||
panning = pan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void row( int key, int inst_idx, int volume_column, int effect, int effect_param ) {
|
|
||||||
effect = effect & 0xFF;
|
|
||||||
if( effect >= 0x30 ) {
|
|
||||||
/* Effects above 0x30 are internal.*/
|
|
||||||
effect = 0;
|
|
||||||
}
|
|
||||||
if( effect == 0x00 && effect_param != 0 ) {
|
|
||||||
/* Arpeggio.*/
|
|
||||||
effect = 0x40;
|
|
||||||
}
|
|
||||||
if( effect == 0x0E ) {
|
|
||||||
/* Renumber 0x0Ex effect command.*/
|
|
||||||
effect = 0x30 + ( ( effect_param & 0xF0 ) >> 4 );
|
|
||||||
effect_param = effect_param & 0x0F;
|
|
||||||
}
|
|
||||||
if( effect == 0x21 ) {
|
|
||||||
/* Renumber 0x21x effect command.*/
|
|
||||||
effect = 0x40 + ( ( effect_param & 0xF0 ) >> 4 );
|
|
||||||
effect_param = effect_param & 0x0F;
|
|
||||||
}
|
|
||||||
current_note[ 0 ] = key;
|
|
||||||
current_note[ 1 ] = inst_idx;
|
|
||||||
current_note[ 2 ] = volume_column;
|
|
||||||
current_note[ 3 ] = effect;
|
|
||||||
current_note[ 4 ] = effect_param;
|
|
||||||
effect_tick = 0;
|
|
||||||
trigger_tick += 1;
|
|
||||||
update_envelopes();
|
|
||||||
key_add = 0;
|
|
||||||
vibrato_add = 0;
|
|
||||||
tremolo_add = 0;
|
|
||||||
if( ! ( effect == 0x3D && effect_param > 0 ) ) {
|
|
||||||
/* Not note delay.*/
|
|
||||||
trigger( key, inst_idx, volume_column, effect );
|
|
||||||
/* Handle volume column.*/
|
|
||||||
switch( volume_column & 0xF0 ) {
|
|
||||||
case 0x00:
|
|
||||||
/* Do nothing.*/
|
|
||||||
break;
|
|
||||||
case 0x60:
|
|
||||||
/* Volume slide down.*/
|
|
||||||
break;
|
|
||||||
case 0x70:
|
|
||||||
/* Volume slide up.*/
|
|
||||||
break;
|
|
||||||
case 0x80:
|
|
||||||
/* Fine volume slide down.*/
|
|
||||||
set_volume( volume - ( volume_column & 0x0F ) );
|
|
||||||
break;
|
|
||||||
case 0x90:
|
|
||||||
/* Fine volume slide up.*/
|
|
||||||
set_volume( volume + ( volume_column & 0x0F ) );
|
|
||||||
break;
|
|
||||||
case 0xA0:
|
|
||||||
/* Set vibrato speed.*/
|
|
||||||
set_vibrato_speed( volume_column & 0x0F );
|
|
||||||
break;
|
|
||||||
case 0xB0:
|
|
||||||
/* Vibrato.*/
|
|
||||||
set_vibrato_depth( volume_column & 0x0F );
|
|
||||||
vibrato();
|
|
||||||
break;
|
|
||||||
case 0xC0:
|
|
||||||
/* Set panning.*/
|
|
||||||
set_panning( ( volume_column & 0x0F ) << 4 );
|
|
||||||
break;
|
|
||||||
case 0xD0:
|
|
||||||
/* Panning slide left.*/
|
|
||||||
break;
|
|
||||||
case 0xE0:
|
|
||||||
/* Panning slide right.*/
|
|
||||||
break;
|
|
||||||
case 0xF0:
|
|
||||||
/* Tone portamento.*/
|
|
||||||
set_portamento_param( volume_column & 0x0F );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
/* Set volume.*/
|
|
||||||
set_volume( volume_column - 0x10 );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if( instrument.vibrato_depth > 0 ) {
|
|
||||||
auto_vibrato();
|
|
||||||
}
|
|
||||||
switch( effect ) {
|
|
||||||
case 0x01:
|
|
||||||
/* Portmento Up.*/
|
|
||||||
set_portamento_param( effect_param );
|
|
||||||
portamento_up();
|
|
||||||
break;
|
|
||||||
case 0x02:
|
|
||||||
/* Portamento Down.*/
|
|
||||||
set_portamento_param( effect_param );
|
|
||||||
portamento_down();
|
|
||||||
break;
|
|
||||||
case 0x03:
|
|
||||||
/* Tone Portamento.*/
|
|
||||||
set_portamento_param( effect_param );
|
|
||||||
break;
|
|
||||||
case 0x04:
|
|
||||||
/* Vibrato.*/
|
|
||||||
set_vibrato_speed( ( effect_param & 0xF0 ) >> 4 );
|
|
||||||
set_vibrato_depth( effect_param & 0x0F );
|
|
||||||
vibrato();
|
|
||||||
break;
|
|
||||||
case 0x05:
|
|
||||||
/* Tone Portamento + Volume Slide.*/
|
|
||||||
set_volume_slide_param( effect_param );
|
|
||||||
volume_slide();
|
|
||||||
break;
|
|
||||||
case 0x06:
|
|
||||||
/* Vibrato + Volume Slide.*/
|
|
||||||
set_volume_slide_param( effect_param );
|
|
||||||
vibrato();
|
|
||||||
volume_slide();
|
|
||||||
break;
|
|
||||||
case 0x07:
|
|
||||||
/* Tremolo.*/
|
|
||||||
set_tremolo_speed( ( effect_param & 0xF0 ) >> 4 );
|
|
||||||
set_tremolo_depth( effect_param & 0x0F );
|
|
||||||
tremolo();
|
|
||||||
break;
|
|
||||||
case 0x08:
|
|
||||||
/* Set Panning.*/
|
|
||||||
set_panning( effect_param );
|
|
||||||
break;
|
|
||||||
case 0x09:
|
|
||||||
/* Set Sample Index.*/
|
|
||||||
set_sample_index( effect_param << 8 );
|
|
||||||
break;
|
|
||||||
case 0x0A:
|
|
||||||
/* Volume Slide.*/
|
|
||||||
set_volume_slide_param( effect_param );
|
|
||||||
volume_slide();
|
|
||||||
break;
|
|
||||||
case 0x0B:
|
|
||||||
/* Pattern Jump.*/
|
|
||||||
break;
|
|
||||||
case 0x0C:
|
|
||||||
/* Set volume.*/
|
|
||||||
set_volume( effect_param );
|
|
||||||
break;
|
|
||||||
case 0x0D:
|
|
||||||
/* Pattern Break.*/
|
|
||||||
break;
|
|
||||||
case 0x0E:
|
|
||||||
/* Extended Commands (See 0x30-0x3F).*/
|
|
||||||
break;
|
|
||||||
case 0x0F:
|
|
||||||
/* Set Speed/Tempo.*/
|
|
||||||
break;
|
|
||||||
case 0x10:
|
|
||||||
/* Set Global Volume.*/
|
|
||||||
set_global_volume( effect_param );
|
|
||||||
break;
|
|
||||||
case 0x11:
|
|
||||||
/* global Volume Slide.*/
|
|
||||||
set_volume_slide_param( effect_param );
|
|
||||||
break;
|
|
||||||
case 0x14:
|
|
||||||
/* Key Off*/
|
|
||||||
if( effect_param == 0 ) {
|
|
||||||
key_on = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x15:
|
|
||||||
/* Set Envelope Tick.*/
|
|
||||||
set_envelope_tick( effect_param );
|
|
||||||
break;
|
|
||||||
case 0x19:
|
|
||||||
/* Panning Slide.*/
|
|
||||||
set_volume_slide_param( effect_param );
|
|
||||||
break;
|
|
||||||
case 0x1B:
|
|
||||||
/* Retrig + Volume Slide.*/
|
|
||||||
set_retrig_param( effect_param );
|
|
||||||
retrig_volume_slide();
|
|
||||||
break;
|
|
||||||
case 0x1D:
|
|
||||||
/* Tremor.*/
|
|
||||||
set_retrig_param( effect_param );
|
|
||||||
tremor();
|
|
||||||
break;
|
|
||||||
case 0x24:
|
|
||||||
/* S3M Fine Vibrato.*/
|
|
||||||
set_vibrato_speed( ( effect_param & 0xF0 ) >> 4 );
|
|
||||||
set_vibrato_depth( effect_param & 0x0F );
|
|
||||||
fine_vibrato();
|
|
||||||
break;
|
|
||||||
case 0x25:
|
|
||||||
/* S3M Set Speed.*/
|
|
||||||
break;
|
|
||||||
case 0x30:
|
|
||||||
/* Amiga Set Filter.*/
|
|
||||||
break;
|
|
||||||
case 0x31:
|
|
||||||
/* Fine Portamento Up.*/
|
|
||||||
set_portamento_param( 0xF0 | effect_param );
|
|
||||||
portamento_up();
|
|
||||||
break;
|
|
||||||
case 0x32:
|
|
||||||
/* Fine Portamento Down.*/
|
|
||||||
set_portamento_param( 0xF0 | effect_param );
|
|
||||||
portamento_down();
|
|
||||||
break;
|
|
||||||
case 0x33:
|
|
||||||
/* Set Glissando Mode.*/
|
|
||||||
break;
|
|
||||||
case 0x34:
|
|
||||||
/* Set Vibrato Waveform.*/
|
|
||||||
set_vibrato_wave( effect_param );
|
|
||||||
break;
|
|
||||||
case 0x35:
|
|
||||||
/* Set Fine Tune.*/
|
|
||||||
break;
|
|
||||||
case 0x36:
|
|
||||||
/* Pattern Loop.*/
|
|
||||||
break;
|
|
||||||
case 0x37:
|
|
||||||
/* Set Tremolo Waveform.*/
|
|
||||||
set_tremolo_wave( effect_param );
|
|
||||||
break;
|
|
||||||
case 0x38:
|
|
||||||
/* Set Panning(Obsolete).*/
|
|
||||||
break;
|
|
||||||
case 0x39:
|
|
||||||
/* Retrig.*/
|
|
||||||
set_retrig_param( effect_param );
|
|
||||||
break;
|
|
||||||
case 0x3A:
|
|
||||||
/* Fine Volume Slide Up.*/
|
|
||||||
set_volume_slide_param( ( effect_param << 4 ) | 0x0F );
|
|
||||||
volume_slide();
|
|
||||||
break;
|
|
||||||
case 0x3B:
|
|
||||||
/* Fine Volume Slide Down.*/
|
|
||||||
set_volume_slide_param( 0xF0 | effect_param );
|
|
||||||
volume_slide();
|
|
||||||
break;
|
|
||||||
case 0x3C:
|
|
||||||
/* Note Cut.*/
|
|
||||||
if( effect_param == 0 ) {
|
|
||||||
set_volume( 0 );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x3D:
|
|
||||||
/* Note Delay.*/
|
|
||||||
break;
|
|
||||||
case 0x3E:
|
|
||||||
/* Pattern Delay.*/
|
|
||||||
break;
|
|
||||||
case 0x3F:
|
|
||||||
/* Invert Loop.*/
|
|
||||||
break;
|
|
||||||
case 0x40:
|
|
||||||
/* Arpeggio.*/
|
|
||||||
break;
|
|
||||||
case 0x41:
|
|
||||||
/* Extra Fine Porta Up.*/
|
|
||||||
set_portamento_param( 0xE0 | effect_param );
|
|
||||||
portamento_up();
|
|
||||||
break;
|
|
||||||
case 0x42:
|
|
||||||
/* Extra Fine Porta Down.*/
|
|
||||||
set_portamento_param( 0xE0 | effect_param );
|
|
||||||
portamento_down();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
calculate_amplitude();
|
|
||||||
calculate_frequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tick() {
|
|
||||||
int volume_column, effect, effect_param;
|
|
||||||
volume_column = current_note[ 2 ];
|
|
||||||
effect = current_note[ 3 ];
|
|
||||||
effect_param = current_note[ 4 ];
|
|
||||||
effect_tick += 1;
|
|
||||||
if( effect == 0x3D && effect_param == effect_tick ) {
|
|
||||||
/* Note delay.*/
|
|
||||||
row( current_note[ 0 ], current_note[ 1 ], volume_column, 0, 0 );
|
|
||||||
} else {
|
|
||||||
trigger_tick += 1;
|
|
||||||
vibrato_tick += 1;
|
|
||||||
tremolo_tick += 1;
|
|
||||||
update_envelopes();
|
|
||||||
key_add = 0;
|
|
||||||
vibrato_add = 0;
|
|
||||||
tremolo_add = 0;
|
|
||||||
if( instrument.vibrato_depth > 0 ) {
|
|
||||||
auto_vibrato();
|
|
||||||
}
|
|
||||||
switch( volume_column & 0xF0 ) {
|
|
||||||
case 0x60:
|
|
||||||
/* Volume Slide Down.*/
|
|
||||||
set_volume( volume - ( volume_column & 0x0F ) );
|
|
||||||
break;
|
|
||||||
case 0x70:
|
|
||||||
/* Volume Slide Up.*/
|
|
||||||
set_volume( volume + ( volume_column & 0x0F ) );
|
|
||||||
break;
|
|
||||||
case 0xB0:
|
|
||||||
/* Vibrato.*/
|
|
||||||
vibrato();
|
|
||||||
break;
|
|
||||||
case 0xD0:
|
|
||||||
/* Panning Slide Left.*/
|
|
||||||
set_panning( panning - ( volume_column & 0x0F ) );
|
|
||||||
break;
|
|
||||||
case 0xE0:
|
|
||||||
/* Panning Slide Right.*/
|
|
||||||
set_panning( panning + ( volume_column & 0x0F ) );
|
|
||||||
break;
|
|
||||||
case 0xF0:
|
|
||||||
/* Tone Portamento.*/
|
|
||||||
tone_portamento();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
switch( effect ) {
|
|
||||||
case 0x01:
|
|
||||||
/* Portamento Up.*/
|
|
||||||
portamento_up();
|
|
||||||
break;
|
|
||||||
case 0x02:
|
|
||||||
/* Portamento Down.*/
|
|
||||||
portamento_down();
|
|
||||||
break;
|
|
||||||
case 0x03:
|
|
||||||
/* Tone Portamento.*/
|
|
||||||
tone_portamento();
|
|
||||||
break;
|
|
||||||
case 0x04:
|
|
||||||
/* Vibrato.*/
|
|
||||||
vibrato();
|
|
||||||
break;
|
|
||||||
case 0x05:
|
|
||||||
/* Tone Portamento + Volume Slide.*/
|
|
||||||
tone_portamento();
|
|
||||||
volume_slide();
|
|
||||||
break;
|
|
||||||
case 0x06:
|
|
||||||
/* Vibrato + Volume Slide */
|
|
||||||
vibrato();
|
|
||||||
volume_slide();
|
|
||||||
break;
|
|
||||||
case 0x07:
|
|
||||||
/* Tremolo.*/
|
|
||||||
tremolo();
|
|
||||||
break;
|
|
||||||
case 0x0A:
|
|
||||||
/* Volume Slide.*/
|
|
||||||
volume_slide();
|
|
||||||
break;
|
|
||||||
case 0x11:
|
|
||||||
/* Global Volume Slide.*/
|
|
||||||
global_volume_slide();
|
|
||||||
break;
|
|
||||||
case 0x14:
|
|
||||||
/* Key off.*/
|
|
||||||
if( effect_tick == effect_param ) {
|
|
||||||
key_on = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x19:
|
|
||||||
/* Panning Slide.*/
|
|
||||||
panning_slide();
|
|
||||||
break;
|
|
||||||
case 0x1B:
|
|
||||||
/* Retrig + Volume Slide.*/
|
|
||||||
retrig_volume_slide();
|
|
||||||
break;
|
|
||||||
case 0x1D:
|
|
||||||
/* Tremor.*/
|
|
||||||
tremor();
|
|
||||||
break;
|
|
||||||
case 0x24:
|
|
||||||
/* S3M Fine Vibrato.*/
|
|
||||||
fine_vibrato();
|
|
||||||
break;
|
|
||||||
case 0x39:
|
|
||||||
/* Retrig.*/
|
|
||||||
retrig_volume_slide();
|
|
||||||
break;
|
|
||||||
case 0x3C:
|
|
||||||
/* Note Cut.*/
|
|
||||||
if( effect_tick == effect_param ) {
|
|
||||||
set_volume( 0 );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x40:
|
|
||||||
/* Arpeggio.*/
|
|
||||||
switch( effect_tick % 3 ) {
|
|
||||||
case 1:
|
|
||||||
key_add = ( effect_param & 0xF0 ) >> 4;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
key_add = effect_param & 0x0F;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
calculate_amplitude();
|
|
||||||
calculate_frequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_vibrato_speed( int speed ) {
|
|
||||||
if( speed > 0 ) {
|
|
||||||
vibrato_speed = speed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_vibrato_depth( int depth ) {
|
|
||||||
if( depth > 0 ) {
|
|
||||||
vibrato_depth = depth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_vibrato_wave( int wave ) {
|
|
||||||
if( wave < 0 || wave > 7 ) {
|
|
||||||
wave = 0;
|
|
||||||
}
|
|
||||||
vibrato_wave = wave;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_tremolo_speed( int speed ) {
|
|
||||||
if( speed > 0 ) {
|
|
||||||
tremolo_speed = speed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_tremolo_depth( int depth ) {
|
|
||||||
if( depth > 0 ) {
|
|
||||||
tremolo_depth = depth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_tremolo_wave( int wave ) {
|
|
||||||
if( wave < 0 || wave > 7 ) {
|
|
||||||
wave = 0;
|
|
||||||
}
|
|
||||||
tremolo_wave = wave;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void vibrato() {
|
|
||||||
int vibrato_phase;
|
|
||||||
vibrato_phase = vibrato_tick * vibrato_speed;
|
|
||||||
vibrato_add += waveform( vibrato_phase, vibrato_wave ) * vibrato_depth >> 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fine_vibrato() {
|
|
||||||
int vibrato_phase;
|
|
||||||
vibrato_phase = vibrato_tick * vibrato_speed;
|
|
||||||
vibrato_add += waveform( vibrato_phase, vibrato_wave ) * vibrato_depth >> 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tremolo() {
|
|
||||||
int tremolo_phase;
|
|
||||||
tremolo_phase = tremolo_tick * tremolo_speed;
|
|
||||||
tremolo_add += waveform( tremolo_phase, tremolo_wave ) * tremolo_depth >> 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_portamento_param( int param ) {
|
|
||||||
if( param != 0 ) {
|
|
||||||
portamento_param = param;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tone_portamento() {
|
|
||||||
int new_period;
|
|
||||||
if( porta_period < period ) {
|
|
||||||
new_period = period - ( portamento_param << 2 );
|
|
||||||
if( new_period < porta_period ) {
|
|
||||||
new_period = porta_period;
|
|
||||||
}
|
|
||||||
set_period( new_period );
|
|
||||||
}
|
|
||||||
if( porta_period > period ) {
|
|
||||||
new_period = period + ( portamento_param << 2 );
|
|
||||||
if( new_period > porta_period ) {
|
|
||||||
new_period = porta_period;
|
|
||||||
}
|
|
||||||
set_period( new_period );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void portamento_up() {
|
|
||||||
if( ( portamento_param & 0xF0 ) == 0xE0 ) {
|
|
||||||
/* Extra-fine porta.*/
|
|
||||||
if( effect_tick == 0 ) {
|
|
||||||
set_period( period - ( portamento_param & 0x0F ) );
|
|
||||||
}
|
|
||||||
} else if( ( portamento_param & 0xF0 ) == 0xF0 ) {
|
|
||||||
/* Fine porta.*/
|
|
||||||
if( effect_tick == 0 ) {
|
|
||||||
set_period( period - ( ( portamento_param & 0x0F ) << 2 ) );
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Normal porta.*/
|
|
||||||
if( effect_tick > 0 ) {
|
|
||||||
set_period( period - ( portamento_param << 2 ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void portamento_down() {
|
|
||||||
if( ( portamento_param & 0xF0 ) == 0xE0 ) {
|
|
||||||
/* Extra-fine porta.*/
|
|
||||||
if( effect_tick == 0 ) {
|
|
||||||
set_period( period + ( portamento_param & 0x0F ) );
|
|
||||||
}
|
|
||||||
} else if( ( portamento_param & 0xF0 ) == 0xF0 ) {
|
|
||||||
/* Fine porta.*/
|
|
||||||
if( effect_tick == 0 ) {
|
|
||||||
set_period( period + ( ( portamento_param & 0x0F ) << 2 ) );
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Normal porta.*/
|
|
||||||
if( effect_tick > 0 ) {
|
|
||||||
set_period( period + ( portamento_param << 2 ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_period( int p ) {
|
|
||||||
if( p < 32 ) {
|
|
||||||
p = 32;
|
|
||||||
}
|
|
||||||
if( p > 32768 ) {
|
|
||||||
p = 32768;
|
|
||||||
}
|
|
||||||
period = p;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_global_volume( int vol ) {
|
|
||||||
if( vol < 0 ) {
|
|
||||||
vol = 0;
|
|
||||||
}
|
|
||||||
if( vol > 64 ) {
|
|
||||||
vol = 64;
|
|
||||||
}
|
|
||||||
global_volume[ 0 ] = vol;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_volume_slide_param( int param ) {
|
|
||||||
if( param != 0 ) {
|
|
||||||
volume_slide_param = param;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void global_volume_slide() {
|
|
||||||
int up, down;
|
|
||||||
up = ( volume_slide_param & 0xF0 ) >> 4;
|
|
||||||
down = volume_slide_param & 0x0F;
|
|
||||||
set_global_volume( global_volume[ 0 ] + up - down );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void volume_slide() {
|
|
||||||
int up, down;
|
|
||||||
up = ( volume_slide_param & 0xF0 ) >> 4;
|
|
||||||
down = volume_slide_param & 0x0F;
|
|
||||||
if( down == 0x0F && up > 0 ) {
|
|
||||||
/* Fine slide up.*/
|
|
||||||
if( effect_tick == 0 ) {
|
|
||||||
set_volume( volume + up );
|
|
||||||
}
|
|
||||||
} else if( up == 0x0F && down > 0 ) {
|
|
||||||
/* Fine slide down.*/
|
|
||||||
if( effect_tick == 0 ) {
|
|
||||||
set_volume( volume - down );
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Normal slide.*/
|
|
||||||
if( effect_tick > 0 || fast_volume_slides ) {
|
|
||||||
set_volume( volume + up - down );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void panning_slide() {
|
|
||||||
int left, right;
|
|
||||||
left = ( volume_slide_param & 0xF0 ) >> 4;
|
|
||||||
right = volume_slide_param & 0x0F;
|
|
||||||
set_panning( panning - left + right );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_retrig_param( int param ) {
|
|
||||||
if( param != 0 ) {
|
|
||||||
retrig_param = param;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tremor() {
|
|
||||||
int on_ticks, cycle_length, cycle_index;
|
|
||||||
on_ticks = ( ( retrig_param & 0xF0 ) >> 4 ) + 1;
|
|
||||||
cycle_length = on_ticks + ( retrig_param & 0x0F ) + 1;
|
|
||||||
cycle_index = trigger_tick % cycle_length;
|
|
||||||
if( cycle_index >= on_ticks ) {
|
|
||||||
tremolo_add = -64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void retrig_volume_slide() {
|
|
||||||
int retrig_volume, retrig_tick;
|
|
||||||
retrig_volume = ( retrig_param & 0xF0 ) >> 4;
|
|
||||||
retrig_tick = retrig_param & 0x0F;
|
|
||||||
if( retrig_tick > 0 && ( trigger_tick % retrig_tick ) == 0 ) {
|
|
||||||
set_sample_index( 0 );
|
|
||||||
switch( retrig_volume ) {
|
|
||||||
case 0x01:
|
|
||||||
set_volume( volume - 1 );
|
|
||||||
break;
|
|
||||||
case 0x02:
|
|
||||||
set_volume( volume - 2 );
|
|
||||||
break;
|
|
||||||
case 0x03:
|
|
||||||
set_volume( volume - 4 );
|
|
||||||
break;
|
|
||||||
case 0x04:
|
|
||||||
set_volume( volume - 8 );
|
|
||||||
break;
|
|
||||||
case 0x05:
|
|
||||||
set_volume( volume - 16 );
|
|
||||||
break;
|
|
||||||
case 0x06:
|
|
||||||
set_volume( volume - volume / 3 );
|
|
||||||
break;
|
|
||||||
case 0x07:
|
|
||||||
set_volume( volume / 2 );
|
|
||||||
break;
|
|
||||||
case 0x09:
|
|
||||||
set_volume( volume + 1 );
|
|
||||||
break;
|
|
||||||
case 0x0A:
|
|
||||||
set_volume( volume + 2 );
|
|
||||||
break;
|
|
||||||
case 0x0B:
|
|
||||||
set_volume( volume + 4 );
|
|
||||||
break;
|
|
||||||
case 0x0C:
|
|
||||||
set_volume( volume + 8 );
|
|
||||||
break;
|
|
||||||
case 0x0D:
|
|
||||||
set_volume( volume + 16 );
|
|
||||||
break;
|
|
||||||
case 0x0E:
|
|
||||||
set_volume( volume + volume / 2 );
|
|
||||||
break;
|
|
||||||
case 0x0F:
|
|
||||||
set_volume( volume * 2 );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_sample_index( int index ) {
|
|
||||||
if( index < 0 ) {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
sample_idx = index;
|
|
||||||
sample_frac = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_envelope_tick( int tick ) {
|
|
||||||
volume_envelope_tick = tick;
|
|
||||||
panning_envelope_tick = tick;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void trigger( int key, int instrument_idx, int volume_column, int effect ) {
|
|
||||||
if( instrument_idx > 0 ) {
|
|
||||||
instrument = module.get_instrument( instrument_idx );
|
|
||||||
sample = instrument.get_sample_from_key( key );
|
|
||||||
set_volume( sample.volume );
|
|
||||||
if( sample.set_panning ) {
|
|
||||||
set_panning( sample.panning );
|
|
||||||
}
|
|
||||||
set_envelope_tick( 0 );
|
|
||||||
fade_out_volume = 32768;
|
|
||||||
key_on = true;
|
|
||||||
}
|
|
||||||
if( key > 0 ) {
|
|
||||||
if( key < 97 ) {
|
|
||||||
porta_period = key_to_period( key );
|
|
||||||
if( effect != 0x03 && effect != 0x05 ) {
|
|
||||||
if( ( volume_column & 0xF0 ) != 0xF0 ) {
|
|
||||||
/* Not portamento.*/
|
|
||||||
trigger_tick = 0;
|
|
||||||
if( vibrato_wave < 4 ) {
|
|
||||||
vibrato_tick = 0;
|
|
||||||
}
|
|
||||||
if( tremolo_wave < 4 ) {
|
|
||||||
tremolo_tick = 0;
|
|
||||||
}
|
|
||||||
set_period( porta_period );
|
|
||||||
set_sample_index( 0 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Key off.*/
|
|
||||||
key_on = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update_envelopes() {
|
|
||||||
Envelope envelope;
|
|
||||||
if( instrument.volume_envelope_active ) {
|
|
||||||
if( !key_on ) {
|
|
||||||
fade_out_volume -= instrument.volume_fade_out & 0xFFFF;
|
|
||||||
if( fade_out_volume < 0 ) {
|
|
||||||
fade_out_volume = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
envelope = instrument.get_volume_envelope();
|
|
||||||
volume_envelope_tick = envelope.next_tick( volume_envelope_tick, key_on );
|
|
||||||
}
|
|
||||||
if( instrument.panning_envelope_active ) {
|
|
||||||
envelope = instrument.get_panning_envelope();
|
|
||||||
panning_envelope_tick = envelope.next_tick( panning_envelope_tick, key_on );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void auto_vibrato() {
|
|
||||||
int sweep, depth, rate;
|
|
||||||
sweep = instrument.vibrato_sweep & 0xFF;
|
|
||||||
depth = instrument.vibrato_depth & 0x0F;
|
|
||||||
rate = instrument.vibrato_rate & 0x3F;
|
|
||||||
if( trigger_tick < sweep ) {
|
|
||||||
depth = depth * trigger_tick / sweep;
|
|
||||||
}
|
|
||||||
vibrato_add += waveform( trigger_tick * rate, 0 ) * depth >> 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int waveform( int phase, int wform ) {
|
|
||||||
int amplitude;
|
|
||||||
amplitude = 0;
|
|
||||||
switch( wform & 0x3 ) {
|
|
||||||
case 0:
|
|
||||||
/* Sine. */
|
|
||||||
if( ( phase & 0x20 ) == 0 ) {
|
|
||||||
amplitude = sine_table[ phase & 0x1F ];
|
|
||||||
} else {
|
|
||||||
amplitude = -sine_table[ phase & 0x1F ];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
/* Saw. */
|
|
||||||
if( ( phase & 0x20 ) == 0 ) {
|
|
||||||
amplitude = ( phase & 0x1F ) << 3;
|
|
||||||
} else {
|
|
||||||
amplitude = ( ( phase & 0x1F ) << 3 ) - 255;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
/* Square. */
|
|
||||||
if( ( phase & 0x20 ) == 0 ) {
|
|
||||||
amplitude = 255;
|
|
||||||
} else {
|
|
||||||
amplitude = -255;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
/* Random. */
|
|
||||||
amplitude = ( random_seed >> 15 ) - 255;
|
|
||||||
random_seed = ( random_seed * 65 + 17 ) & 0xFFFFFF;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return amplitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int key_to_period( int key ) {
|
|
||||||
int octave, log_2_period, period_out;
|
|
||||||
octave = ( key << IBXM.FP_SHIFT ) / 12 + sample.transpose;
|
|
||||||
if( linear_periods ) {
|
|
||||||
period_out = 7744 - ( octave * 768 >> IBXM.FP_SHIFT );
|
|
||||||
} else {
|
|
||||||
log_2_period = LOG_2_29024 - octave;
|
|
||||||
period_out = LogTable.raise_2( log_2_period );
|
|
||||||
period_out = period_out >> ( IBXM.FP_SHIFT - 1 );
|
|
||||||
period_out = ( period_out >> 1 ) + ( period_out & 1 );
|
|
||||||
}
|
|
||||||
return period_out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void calculate_amplitude() {
|
|
||||||
int envelope_volume, tremolo_volume, amplitude;
|
|
||||||
int envelope_panning, mixer_panning, panning_range;
|
|
||||||
Envelope envelope;
|
|
||||||
envelope_volume = 0;
|
|
||||||
if( instrument.volume_envelope_active ) {
|
|
||||||
envelope = instrument.get_volume_envelope();
|
|
||||||
envelope_volume = envelope.calculate_ampl( volume_envelope_tick );
|
|
||||||
} else {
|
|
||||||
if( key_on ) {
|
|
||||||
envelope_volume = 64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tremolo_volume = volume + tremolo_add;
|
|
||||||
if( tremolo_volume < 0 ) {
|
|
||||||
tremolo_volume = 0;
|
|
||||||
}
|
|
||||||
if( tremolo_volume > 64 ) {
|
|
||||||
tremolo_volume = 64;
|
|
||||||
}
|
|
||||||
amplitude = tremolo_volume << IBXM.FP_SHIFT - 6;
|
|
||||||
amplitude = amplitude * envelope_volume >> 6;
|
|
||||||
amplitude = amplitude * fade_out_volume >> 15;
|
|
||||||
amplitude = amplitude * global_volume[ 0 ] >> 6;
|
|
||||||
amplitude = amplitude * module.channel_gain >> IBXM.FP_SHIFT;
|
|
||||||
silent = sample.has_finished( sample_idx );
|
|
||||||
if( amplitude <= 0 ) {
|
|
||||||
silent = true;
|
|
||||||
} else {
|
|
||||||
envelope_panning = 32;
|
|
||||||
if( instrument.panning_envelope_active ) {
|
|
||||||
envelope = instrument.get_panning_envelope();
|
|
||||||
envelope_panning = envelope.calculate_ampl( panning_envelope_tick );
|
|
||||||
}
|
|
||||||
mixer_panning = ( panning & 0xFF ) << IBXM.FP_SHIFT - 8;
|
|
||||||
panning_range = IBXM.FP_ONE - mixer_panning;
|
|
||||||
if( panning_range > mixer_panning ) {
|
|
||||||
panning_range = mixer_panning;
|
|
||||||
}
|
|
||||||
mixer_panning = mixer_panning + ( panning_range * ( envelope_panning - 32 ) >> 5 );
|
|
||||||
left_gain = amplitude * ( IBXM.FP_ONE - mixer_panning ) >> IBXM.FP_SHIFT;
|
|
||||||
right_gain = amplitude * mixer_panning >> IBXM.FP_SHIFT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void calculate_frequency() {
|
|
||||||
int vibrato_period, log_2_freq;
|
|
||||||
vibrato_period = period + vibrato_add;
|
|
||||||
if( vibrato_period < 32 ) {
|
|
||||||
vibrato_period = 32;
|
|
||||||
}
|
|
||||||
if( vibrato_period > 32768 ) {
|
|
||||||
vibrato_period = 32768;
|
|
||||||
}
|
|
||||||
if( linear_periods ) {
|
|
||||||
log_2_freq = LOG_2_8363 + ( 4608 - vibrato_period << IBXM.FP_SHIFT ) / 768;
|
|
||||||
} else {
|
|
||||||
log_2_freq = module.pal ? LOG_2_8287 : LOG_2_8363;
|
|
||||||
log_2_freq = log_2_freq + LOG_2_1712 - LogTable.log_2( vibrato_period );
|
|
||||||
}
|
|
||||||
log_2_freq += ( key_add << IBXM.FP_SHIFT ) / 12;
|
|
||||||
step = LogTable.raise_2( log_2_freq - log_2_sampling_rate );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,111 +0,0 @@
|
||||||
|
|
||||||
package ibxm;
|
|
||||||
|
|
||||||
public class Envelope {
|
|
||||||
public boolean sustain, looped;
|
|
||||||
private int sustain_tick, loop_start_tick, loop_end_tick;
|
|
||||||
private int[] ticks, ampls;
|
|
||||||
|
|
||||||
public Envelope() {
|
|
||||||
set_num_points( 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_num_points( int num_points ) {
|
|
||||||
//int point; Forge: Unused
|
|
||||||
if( num_points <= 0 ) {
|
|
||||||
num_points = 1;
|
|
||||||
}
|
|
||||||
ticks = new int[ num_points ];
|
|
||||||
ampls = new int[ num_points ];
|
|
||||||
set_point( 0, 0, 0, false );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* When you set a point, all subsequent points are reset. */
|
|
||||||
public void set_point( int point, int tick, int ampl, boolean delta ) {
|
|
||||||
if( point >= 0 && point < ticks.length ) {
|
|
||||||
if( point == 0 ) {
|
|
||||||
tick = 0;
|
|
||||||
}
|
|
||||||
if( point > 0 ) {
|
|
||||||
if( delta ) tick += ticks[ point - 1 ];
|
|
||||||
if( tick <= ticks[ point - 1 ] ) {
|
|
||||||
System.out.println( "Envelope: Point not valid (" + tick + " <= " + ticks[ point - 1 ] + ")");
|
|
||||||
tick = ticks[ point - 1 ] + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ticks[ point ] = tick;
|
|
||||||
ampls[ point ] = ampl;
|
|
||||||
point += 1;
|
|
||||||
while( point < ticks.length ) {
|
|
||||||
ticks[ point ] = ticks[ point - 1 ] + 1;
|
|
||||||
ampls[ point ] = 0;
|
|
||||||
point += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_sustain_point( int point ) {
|
|
||||||
if( point < 0 ) {
|
|
||||||
point = 0;
|
|
||||||
}
|
|
||||||
if( point >= ticks.length ) {
|
|
||||||
point = ticks.length - 1;
|
|
||||||
}
|
|
||||||
sustain_tick = ticks[ point ];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_loop_points( int start, int end ) {
|
|
||||||
if( start < 0 ) {
|
|
||||||
start = 0;
|
|
||||||
}
|
|
||||||
if( start >= ticks.length ) {
|
|
||||||
start = ticks.length - 1;
|
|
||||||
}
|
|
||||||
if( end < start || end >= ticks.length ) {
|
|
||||||
end = start;
|
|
||||||
}
|
|
||||||
loop_start_tick = ticks[ start ];
|
|
||||||
loop_end_tick = ticks[ end ];
|
|
||||||
}
|
|
||||||
|
|
||||||
public int next_tick( int tick, boolean key_on ) {
|
|
||||||
tick = tick + 1;
|
|
||||||
if( looped && tick >= loop_end_tick ) {
|
|
||||||
tick = loop_start_tick;
|
|
||||||
}
|
|
||||||
if( sustain && key_on && tick >= sustain_tick ) {
|
|
||||||
tick = sustain_tick;
|
|
||||||
}
|
|
||||||
return tick;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int calculate_ampl( int tick ) {
|
|
||||||
int idx, point, delta_t, delta_a, ampl;
|
|
||||||
ampl = ampls[ ticks.length - 1 ];
|
|
||||||
if( tick < ticks[ ticks.length - 1 ] ) {
|
|
||||||
point = 0;
|
|
||||||
for( idx = 1; idx < ticks.length; idx++ ) {
|
|
||||||
if( ticks[ idx ] <= tick ) {
|
|
||||||
point = idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delta_t = ticks[ point + 1 ] - ticks[ point ];
|
|
||||||
delta_a = ampls[ point + 1 ] - ampls[ point ];
|
|
||||||
ampl = ( delta_a << IBXM.FP_SHIFT ) / delta_t;
|
|
||||||
ampl = ampl * ( tick - ticks[ point ] ) >> IBXM.FP_SHIFT;
|
|
||||||
ampl = ampl + ampls[ point ];
|
|
||||||
}
|
|
||||||
return ampl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dump() {
|
|
||||||
int idx, tick;
|
|
||||||
for( idx = 0; idx < ticks.length; idx++ ) {
|
|
||||||
System.out.println( ticks[ idx ] + ", " + ampls[ idx ] );
|
|
||||||
}
|
|
||||||
for( tick = 0; tick < 222; tick++ ) {
|
|
||||||
System.out.print( calculate_ampl( tick ) + ", " );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,257 +0,0 @@
|
||||||
|
|
||||||
package ibxm;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
public class FastTracker2 {
|
|
||||||
public static boolean is_xm( byte[] header_60_bytes ) {
|
|
||||||
String xm_identifier;
|
|
||||||
xm_identifier = ascii_text( header_60_bytes, 0, 17 );
|
|
||||||
return xm_identifier.equals( "Extended Module: " );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Module load_xm( byte[] header_60_bytes, DataInput data_input ) throws IOException {
|
|
||||||
int xm_version, song_header_length, sequence_length;
|
|
||||||
int num_channels, num_patterns, num_instruments, xm_flags, idx;
|
|
||||||
byte[] structure_header, song_header;
|
|
||||||
boolean delta_env;
|
|
||||||
String tracker_name;
|
|
||||||
Instrument instrument;
|
|
||||||
Module module;
|
|
||||||
if( !is_xm( header_60_bytes ) ) {
|
|
||||||
throw new IllegalArgumentException( "Not an XM file!" );
|
|
||||||
}
|
|
||||||
xm_version = unsigned_short_le( header_60_bytes, 58 );
|
|
||||||
if( xm_version != 0x0104 ) {
|
|
||||||
throw new IllegalArgumentException( "Sorry, XM version " + xm_version + " is not supported!" );
|
|
||||||
}
|
|
||||||
module = new Module();
|
|
||||||
module.song_title = ascii_text( header_60_bytes, 17, 20 );
|
|
||||||
tracker_name = ascii_text( header_60_bytes, 38, 20 );
|
|
||||||
delta_env = tracker_name.startsWith( "DigiBooster Pro" );
|
|
||||||
structure_header = new byte[ 4 ];
|
|
||||||
data_input.readFully( structure_header );
|
|
||||||
song_header_length = int_le( structure_header, 0 );
|
|
||||||
song_header = new byte[ song_header_length ];
|
|
||||||
data_input.readFully( song_header, 4, song_header_length - 4 );
|
|
||||||
sequence_length = unsigned_short_le( song_header, 4 );
|
|
||||||
module.restart_sequence_index = unsigned_short_le( song_header, 6 );
|
|
||||||
num_channels = unsigned_short_le( song_header, 8 );
|
|
||||||
num_patterns = unsigned_short_le( song_header, 10 );
|
|
||||||
num_instruments = unsigned_short_le( song_header, 12 );
|
|
||||||
xm_flags = unsigned_short_le( song_header, 14 );
|
|
||||||
module.linear_periods = ( xm_flags & 0x1 ) == 0x1;
|
|
||||||
module.global_volume = 64;
|
|
||||||
module.channel_gain = IBXM.FP_ONE * 3 / 8;
|
|
||||||
module.default_speed = unsigned_short_le( song_header, 16 );
|
|
||||||
module.default_tempo = unsigned_short_le( song_header, 18 );
|
|
||||||
module.set_num_channels( num_channels );
|
|
||||||
for( idx = 0; idx < num_channels; idx++ ) {
|
|
||||||
module.set_initial_panning( idx, 128 );
|
|
||||||
}
|
|
||||||
module.set_sequence_length( sequence_length );
|
|
||||||
for( idx = 0; idx < sequence_length; idx++ ) {
|
|
||||||
module.set_sequence( idx, song_header[ 20 + idx ] & 0xFF );
|
|
||||||
}
|
|
||||||
module.set_num_patterns( num_patterns );
|
|
||||||
for( idx = 0; idx < num_patterns; idx++ ) {
|
|
||||||
module.set_pattern( idx, read_xm_pattern( data_input, num_channels ) );
|
|
||||||
}
|
|
||||||
module.set_num_instruments( num_instruments );
|
|
||||||
for( idx = 1; idx <= num_instruments; idx++ ) {
|
|
||||||
try {
|
|
||||||
instrument = read_xm_instrument( data_input, delta_env );
|
|
||||||
module.set_instrument( idx, instrument );
|
|
||||||
} catch( EOFException e ) {
|
|
||||||
System.out.println( "Instrument " + idx + " is missing!" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Pattern read_xm_pattern( DataInput data_input, int num_channels ) throws IOException {
|
|
||||||
@SuppressWarnings("unused") //Forge
|
|
||||||
int pattern_header_length, packing_type, num_rows, pattern_data_length;
|
|
||||||
byte[] structure_header, pattern_header, pattern_data;
|
|
||||||
Pattern pattern;
|
|
||||||
structure_header = new byte[ 4 ];
|
|
||||||
data_input.readFully( structure_header );
|
|
||||||
pattern_header_length = int_le( structure_header, 0 );
|
|
||||||
pattern_header = new byte[ pattern_header_length ];
|
|
||||||
data_input.readFully( pattern_header, 4, pattern_header_length - 4 );
|
|
||||||
packing_type = pattern_header[ 4 ];
|
|
||||||
if( packing_type != 0 ) {
|
|
||||||
throw new IllegalArgumentException( "Pattern packing type " + packing_type + " is not supported!" );
|
|
||||||
}
|
|
||||||
pattern = new Pattern();
|
|
||||||
pattern.num_rows = unsigned_short_le( pattern_header, 5 );
|
|
||||||
pattern_data_length = unsigned_short_le( pattern_header, 7 );
|
|
||||||
pattern_data = new byte[ pattern_data_length ];
|
|
||||||
data_input.readFully( pattern_data );
|
|
||||||
pattern.set_pattern_data( pattern_data );
|
|
||||||
return pattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Instrument read_xm_instrument( DataInput data_input, boolean delta_env ) throws IOException {
|
|
||||||
int instrument_header_length, num_samples, idx;
|
|
||||||
int env_tick, env_ampl, env_num_points, flags;
|
|
||||||
byte[] structure_header, instrument_header, sample_headers;
|
|
||||||
Instrument instrument;
|
|
||||||
Envelope envelope;
|
|
||||||
structure_header = new byte[ 4 ];
|
|
||||||
data_input.readFully( structure_header );
|
|
||||||
instrument_header_length = int_le( structure_header, 0 );
|
|
||||||
instrument_header = new byte[ instrument_header_length ];
|
|
||||||
data_input.readFully( instrument_header, 4, instrument_header_length - 4 );
|
|
||||||
instrument = new Instrument();
|
|
||||||
instrument.name = ascii_text( instrument_header, 4, 22 );
|
|
||||||
num_samples = unsigned_short_le( instrument_header, 27 );
|
|
||||||
if( num_samples > 0 ) {
|
|
||||||
instrument.set_num_samples( num_samples );
|
|
||||||
for( idx = 0; idx < 96; idx++ ) {
|
|
||||||
instrument.set_key_to_sample( idx + 1, instrument_header[ 33 + idx ] & 0xFF );
|
|
||||||
}
|
|
||||||
envelope = new Envelope();
|
|
||||||
env_num_points = instrument_header[ 225 ] & 0xFF;
|
|
||||||
envelope.set_num_points( env_num_points );
|
|
||||||
for( idx = 0; idx < env_num_points; idx++ ) {
|
|
||||||
env_tick = unsigned_short_le( instrument_header, 129 + idx * 4 );
|
|
||||||
env_ampl = unsigned_short_le( instrument_header, 131 + idx * 4 );
|
|
||||||
envelope.set_point( idx, env_tick, env_ampl, delta_env );
|
|
||||||
}
|
|
||||||
envelope.set_sustain_point( instrument_header[ 227 ] & 0xFF );
|
|
||||||
envelope.set_loop_points( instrument_header[ 228 ] & 0xFF, instrument_header[ 229 ] & 0xFF );
|
|
||||||
flags = instrument_header[ 233 ] & 0xFF;
|
|
||||||
instrument.volume_envelope_active = ( flags & 0x1 ) == 0x1;
|
|
||||||
envelope.sustain = ( flags & 0x2 ) == 0x2;
|
|
||||||
envelope.looped = ( flags & 0x4 ) == 0x4;
|
|
||||||
instrument.set_volume_envelope( envelope );
|
|
||||||
envelope = new Envelope();
|
|
||||||
env_num_points = instrument_header[ 226 ] & 0xFF;
|
|
||||||
envelope.set_num_points( env_num_points );
|
|
||||||
for( idx = 0; idx < env_num_points; idx++ ) {
|
|
||||||
env_tick = unsigned_short_le( instrument_header, 177 + idx * 4 );
|
|
||||||
env_ampl = unsigned_short_le( instrument_header, 179 + idx * 4 );
|
|
||||||
envelope.set_point( idx, env_tick, env_ampl, delta_env );
|
|
||||||
}
|
|
||||||
envelope.set_sustain_point( instrument_header[ 230 ] & 0xFF );
|
|
||||||
envelope.set_loop_points( instrument_header[ 231 ] & 0xFF, instrument_header[ 232 ] & 0xFF );
|
|
||||||
flags = instrument_header[ 234 ] & 0xFF;
|
|
||||||
instrument.panning_envelope_active = ( flags & 0x1 ) == 0x1;
|
|
||||||
envelope.sustain = ( flags & 0x2 ) == 0x2;
|
|
||||||
envelope.looped = ( flags & 0x4 ) == 0x4;
|
|
||||||
instrument.set_panning_envelope( envelope );
|
|
||||||
instrument.vibrato_type = instrument_header[ 235 ] & 0xFF;
|
|
||||||
instrument.vibrato_sweep = instrument_header[ 236 ] & 0xFF;
|
|
||||||
instrument.vibrato_depth = instrument_header[ 237 ] & 0xFF;
|
|
||||||
instrument.vibrato_rate = instrument_header[ 238 ] & 0xFF;
|
|
||||||
instrument.volume_fade_out = unsigned_short_le( instrument_header, 239 );
|
|
||||||
sample_headers = new byte[ num_samples * 40 ];
|
|
||||||
data_input.readFully( sample_headers );
|
|
||||||
for( idx = 0; idx < num_samples; idx++ ) {
|
|
||||||
instrument.set_sample( idx, read_xm_sample( sample_headers, idx, data_input ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return instrument;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Sample read_xm_sample( byte[] sample_headers, int sample_idx, DataInput data_input ) throws IOException {
|
|
||||||
int header_offset, sample_length, loop_start, loop_length;
|
|
||||||
int flags, in_idx, out_idx, sam, last_sam;
|
|
||||||
int fine_tune, relative_note;
|
|
||||||
boolean sixteen_bit, ping_pong;
|
|
||||||
byte[] raw_sample_data;
|
|
||||||
short[] decoded_sample_data;
|
|
||||||
Sample sample;
|
|
||||||
header_offset = sample_idx * 40;
|
|
||||||
sample = new Sample();
|
|
||||||
sample_length = int_le( sample_headers, header_offset );
|
|
||||||
loop_start = int_le( sample_headers, header_offset + 4 );
|
|
||||||
loop_length = int_le( sample_headers, header_offset + 8 );
|
|
||||||
sample.volume = sample_headers[ header_offset + 12 ] & 0xFF;
|
|
||||||
fine_tune = sample_headers[ header_offset + 13 ];
|
|
||||||
fine_tune = ( fine_tune << IBXM.FP_SHIFT ) / 1536;
|
|
||||||
sample.set_panning = true;
|
|
||||||
flags = sample_headers[ header_offset + 14 ] & 0xFF;
|
|
||||||
if( ( flags & 0x03 ) == 0 ) {
|
|
||||||
loop_length = 0;
|
|
||||||
}
|
|
||||||
ping_pong = ( flags & 0x02 ) == 0x02;
|
|
||||||
sixteen_bit = ( flags & 0x10 ) == 0x10;
|
|
||||||
sample.panning = sample_headers[ header_offset + 15 ] & 0xFF;
|
|
||||||
relative_note = sample_headers[ header_offset + 16 ];
|
|
||||||
relative_note = ( relative_note << IBXM.FP_SHIFT ) / 12;
|
|
||||||
sample.transpose = relative_note + fine_tune;
|
|
||||||
sample.name = ascii_text( sample_headers, header_offset + 18, 22 );
|
|
||||||
raw_sample_data = new byte[ sample_length ];
|
|
||||||
try {
|
|
||||||
data_input.readFully( raw_sample_data );
|
|
||||||
} catch( EOFException e ) {
|
|
||||||
System.out.println( "Sample has been truncated!" );
|
|
||||||
}
|
|
||||||
in_idx = 0;
|
|
||||||
out_idx = 0;
|
|
||||||
sam = 0;
|
|
||||||
last_sam = 0;
|
|
||||||
if( sixteen_bit ) {
|
|
||||||
decoded_sample_data = new short[ sample_length >> 1 ];
|
|
||||||
while( in_idx < raw_sample_data.length ) {
|
|
||||||
sam = raw_sample_data[ in_idx ] & 0xFF;
|
|
||||||
sam = sam | ( ( raw_sample_data[ in_idx + 1 ] & 0xFF ) << 8 );
|
|
||||||
last_sam = last_sam + sam;
|
|
||||||
decoded_sample_data[ out_idx ] = ( short ) last_sam;
|
|
||||||
in_idx += 2;
|
|
||||||
out_idx += 1;
|
|
||||||
}
|
|
||||||
sample.set_sample_data( decoded_sample_data, loop_start >> 1, loop_length >> 1, ping_pong );
|
|
||||||
} else {
|
|
||||||
decoded_sample_data = new short[ sample_length ];
|
|
||||||
while( in_idx < raw_sample_data.length ) {
|
|
||||||
sam = raw_sample_data[ in_idx ] & 0xFF;
|
|
||||||
last_sam = last_sam + sam;
|
|
||||||
decoded_sample_data[ out_idx ] = ( short ) ( last_sam << 8 );
|
|
||||||
in_idx += 1;
|
|
||||||
out_idx += 1;
|
|
||||||
}
|
|
||||||
sample.set_sample_data( decoded_sample_data, loop_start, loop_length, ping_pong );
|
|
||||||
}
|
|
||||||
return sample;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int unsigned_short_le( byte[] buffer, int offset ) {
|
|
||||||
int value;
|
|
||||||
value = buffer[ offset ] & 0xFF;
|
|
||||||
value = value | ( ( buffer[ offset + 1 ] & 0xFF ) << 8 );
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int int_le( byte[] buffer, int offset ) {
|
|
||||||
int value;
|
|
||||||
value = buffer[ offset ] & 0xFF;
|
|
||||||
value = value | ( ( buffer[ offset + 1 ] & 0xFF ) << 8 );
|
|
||||||
value = value | ( ( buffer[ offset + 2 ] & 0xFF ) << 16 );
|
|
||||||
value = value | ( ( buffer[ offset + 3 ] & 0x7F ) << 24 );
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String ascii_text( byte[] buffer, int offset, int length ) {
|
|
||||||
int idx, chr;
|
|
||||||
byte[] string_buffer;
|
|
||||||
String string;
|
|
||||||
string_buffer = new byte[ length ];
|
|
||||||
for( idx = 0; idx < length; idx++ ) {
|
|
||||||
chr = buffer[ offset + idx ];
|
|
||||||
if( chr < 32 ) {
|
|
||||||
chr = 32;
|
|
||||||
}
|
|
||||||
string_buffer[ idx ] = ( byte ) chr;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
string = new String( string_buffer, 0, length, "ISO-8859-1" );
|
|
||||||
} catch( UnsupportedEncodingException e ) {
|
|
||||||
string = "";
|
|
||||||
}
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,343 +0,0 @@
|
||||||
|
|
||||||
package ibxm;
|
|
||||||
|
|
||||||
public class IBXM {
|
|
||||||
public static final String VERSION = "ibxm alpha 51 (c)2008 mumart@gmail.com";
|
|
||||||
|
|
||||||
public static final int FP_SHIFT = 15;
|
|
||||||
public static final int FP_ONE = 1 << FP_SHIFT;
|
|
||||||
public static final int FP_MASK = FP_ONE - 1;
|
|
||||||
|
|
||||||
private int sampling_rate, resampling_quality, volume_ramp_length;
|
|
||||||
private int tick_length_samples, current_tick_samples;
|
|
||||||
private int[] mixing_buffer, volume_ramp_buffer;
|
|
||||||
|
|
||||||
private Module module;
|
|
||||||
private Channel[] channels;
|
|
||||||
private int[] global_volume, note;
|
|
||||||
private int current_sequence_index, next_sequence_index;
|
|
||||||
private int current_row, next_row;
|
|
||||||
private int tick_counter, ticks_per_row;
|
|
||||||
private int pattern_loop_count, pattern_loop_channel;
|
|
||||||
|
|
||||||
public IBXM( int sample_rate ) {
|
|
||||||
|
|
||||||
/** MODIFIED 13 Oct 2009 by Paul Lamb **/
|
|
||||||
// System.out.println( VERSION );
|
|
||||||
/***************************************/
|
|
||||||
|
|
||||||
if( sample_rate < 8000 ) {
|
|
||||||
sample_rate = 8000;
|
|
||||||
}
|
|
||||||
sampling_rate = sample_rate;
|
|
||||||
volume_ramp_length = sampling_rate >> 10;
|
|
||||||
volume_ramp_buffer = new int[ volume_ramp_length * 2 ];
|
|
||||||
mixing_buffer = new int[ sampling_rate / 6 ];
|
|
||||||
global_volume = new int[ 1 ];
|
|
||||||
note = new int[ 5 ];
|
|
||||||
set_module( new Module() );
|
|
||||||
set_resampling_quality( 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_module( Module m ) {
|
|
||||||
int channel_idx;
|
|
||||||
module = m;
|
|
||||||
channels = new Channel[ module.get_num_channels() ];
|
|
||||||
for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) {
|
|
||||||
channels[ channel_idx ] = new Channel( module, sampling_rate, global_volume );
|
|
||||||
}
|
|
||||||
set_sequence_index( 0, 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_resampling_quality( int quality ) {
|
|
||||||
resampling_quality = quality;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int calculate_song_duration() {
|
|
||||||
int song_duration;
|
|
||||||
set_sequence_index( 0, 0 );
|
|
||||||
next_tick();
|
|
||||||
song_duration = tick_length_samples;
|
|
||||||
while( !next_tick() ) {
|
|
||||||
song_duration += tick_length_samples;
|
|
||||||
}
|
|
||||||
set_sequence_index( 0, 0 );
|
|
||||||
return song_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_sequence_index( int sequence_index, int row ) {
|
|
||||||
int channel_idx;
|
|
||||||
global_volume[ 0 ] = 64;
|
|
||||||
for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) {
|
|
||||||
channels[ channel_idx ].reset();
|
|
||||||
channels[ channel_idx ].set_panning( module.get_initial_panning( channel_idx ) );
|
|
||||||
}
|
|
||||||
set_global_volume( module.global_volume );
|
|
||||||
set_speed( 6 );
|
|
||||||
set_speed( module.default_speed );
|
|
||||||
set_tempo( 125 );
|
|
||||||
set_tempo( module.default_tempo );
|
|
||||||
pattern_loop_count = -1;
|
|
||||||
next_sequence_index = sequence_index;
|
|
||||||
next_row = row;
|
|
||||||
tick_counter = 0;
|
|
||||||
current_tick_samples = tick_length_samples;
|
|
||||||
clear_vol_ramp_buffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void seek( int sample_position ) {
|
|
||||||
//int idx; Forge, Unused
|
|
||||||
set_sequence_index( 0, 0 );
|
|
||||||
next_tick();
|
|
||||||
while( sample_position > tick_length_samples ) {
|
|
||||||
sample_position -= tick_length_samples;
|
|
||||||
next_tick();
|
|
||||||
}
|
|
||||||
mix_tick();
|
|
||||||
current_tick_samples = sample_position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void get_audio( byte[] output_buffer, int frames ) {
|
|
||||||
int output_idx, mix_idx, mix_end, count, amplitude;
|
|
||||||
output_idx = 0;
|
|
||||||
while( frames > 0 ) {
|
|
||||||
count = tick_length_samples - current_tick_samples;
|
|
||||||
if( count > frames ) {
|
|
||||||
count = frames;
|
|
||||||
}
|
|
||||||
mix_idx = current_tick_samples << 1;
|
|
||||||
mix_end = mix_idx + ( count << 1 ) - 1;
|
|
||||||
while( mix_idx <= mix_end ) {
|
|
||||||
amplitude = mixing_buffer[ mix_idx ];
|
|
||||||
if( amplitude > 32767 ) {
|
|
||||||
amplitude = 32767;
|
|
||||||
}
|
|
||||||
if( amplitude < -32768 ) {
|
|
||||||
amplitude = -32768;
|
|
||||||
}
|
|
||||||
output_buffer[ output_idx ] = ( byte ) ( amplitude >> 8 );
|
|
||||||
output_buffer[ output_idx + 1 ] = ( byte ) ( amplitude & 0xFF );
|
|
||||||
output_idx += 2;
|
|
||||||
mix_idx += 1;
|
|
||||||
}
|
|
||||||
current_tick_samples = mix_idx >> 1;
|
|
||||||
frames -= count;
|
|
||||||
if( frames > 0 ) {
|
|
||||||
next_tick();
|
|
||||||
mix_tick();
|
|
||||||
current_tick_samples = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void mix_tick() {
|
|
||||||
int channel_idx, mix_idx, mix_len;
|
|
||||||
mix_idx = 0;
|
|
||||||
mix_len = tick_length_samples + volume_ramp_length << 1;
|
|
||||||
while( mix_idx < mix_len ) {
|
|
||||||
mixing_buffer[ mix_idx ] = 0;
|
|
||||||
mix_idx += 1;
|
|
||||||
}
|
|
||||||
for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) {
|
|
||||||
mix_len = tick_length_samples + volume_ramp_length;
|
|
||||||
channels[ channel_idx ].resample( mixing_buffer, 0, mix_len, resampling_quality );
|
|
||||||
}
|
|
||||||
volume_ramp();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean next_tick() {
|
|
||||||
int channel_idx;
|
|
||||||
boolean song_end;
|
|
||||||
for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) {
|
|
||||||
channels[ channel_idx ].update_sample_idx( tick_length_samples );
|
|
||||||
}
|
|
||||||
tick_counter -= 1;
|
|
||||||
if( tick_counter <= 0 ) {
|
|
||||||
tick_counter = ticks_per_row;
|
|
||||||
song_end = next_row();
|
|
||||||
} else {
|
|
||||||
for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) {
|
|
||||||
channels[ channel_idx ].tick();
|
|
||||||
}
|
|
||||||
song_end = false;
|
|
||||||
}
|
|
||||||
return song_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean next_row() {
|
|
||||||
int channel_idx, effect, effect_param;
|
|
||||||
boolean song_end;
|
|
||||||
Pattern pattern;
|
|
||||||
song_end = false;
|
|
||||||
if( next_sequence_index < 0 ) {
|
|
||||||
/* Bad next sequence index.*/
|
|
||||||
next_sequence_index = 0;
|
|
||||||
next_row = 0;
|
|
||||||
}
|
|
||||||
if( next_sequence_index >= module.get_sequence_length() ) {
|
|
||||||
/* End of sequence.*/
|
|
||||||
song_end = true;
|
|
||||||
next_sequence_index = module.restart_sequence_index;
|
|
||||||
if( next_sequence_index < 0 ) {
|
|
||||||
next_sequence_index = 0;
|
|
||||||
}
|
|
||||||
if( next_sequence_index >= module.get_sequence_length() ) {
|
|
||||||
next_sequence_index = 0;
|
|
||||||
}
|
|
||||||
next_row = 0;
|
|
||||||
}
|
|
||||||
if( next_sequence_index < current_sequence_index ) {
|
|
||||||
/* Jump to previous pattern. */
|
|
||||||
song_end = true;
|
|
||||||
}
|
|
||||||
if( next_sequence_index == current_sequence_index ) {
|
|
||||||
if( next_row <= current_row ) {
|
|
||||||
if( pattern_loop_count < 0 ) {
|
|
||||||
/* Jump to previous row in the same pattern, but not a pattern loop. */
|
|
||||||
song_end = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
current_sequence_index = next_sequence_index;
|
|
||||||
pattern = module.get_pattern_from_sequence( current_sequence_index );
|
|
||||||
if( next_row < 0 || next_row >= pattern.num_rows ) {
|
|
||||||
/* Bad next row.*/
|
|
||||||
next_row = 0;
|
|
||||||
}
|
|
||||||
current_row = next_row;
|
|
||||||
next_row = current_row + 1;
|
|
||||||
if( next_row >= pattern.num_rows ) {
|
|
||||||
next_sequence_index = current_sequence_index + 1;
|
|
||||||
next_row = 0;
|
|
||||||
}
|
|
||||||
for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) {
|
|
||||||
pattern.get_note( note, current_row * channels.length + channel_idx );
|
|
||||||
effect = note[ 3 ];
|
|
||||||
effect_param = note[ 4 ];
|
|
||||||
channels[ channel_idx ].row( note[ 0 ], note[ 1 ], note[ 2 ], effect, effect_param );
|
|
||||||
switch( effect ) {
|
|
||||||
case 0x0B:
|
|
||||||
/* Pattern Jump.*/
|
|
||||||
if( pattern_loop_count < 0 ) {
|
|
||||||
next_sequence_index = effect_param;
|
|
||||||
next_row = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x0D:
|
|
||||||
/* Pattern Break.*/
|
|
||||||
if( pattern_loop_count < 0 ) {
|
|
||||||
next_sequence_index = current_sequence_index + 1;
|
|
||||||
next_row = ( effect_param >> 4 ) * 10 + ( effect_param & 0x0F );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x0E:
|
|
||||||
/* Extended.*/
|
|
||||||
switch( effect_param & 0xF0 ) {
|
|
||||||
case 0x60:
|
|
||||||
/* Pattern loop.*/
|
|
||||||
if( ( effect_param & 0x0F ) == 0 ) {
|
|
||||||
/* Set loop marker on this channel. */
|
|
||||||
channels[ channel_idx ].pattern_loop_row = current_row;
|
|
||||||
}
|
|
||||||
if( channels[ channel_idx ].pattern_loop_row < current_row ) {
|
|
||||||
/* Marker and parameter are valid. Begin looping. */
|
|
||||||
if( pattern_loop_count < 0 ) {
|
|
||||||
/* Not already looping, begin. */
|
|
||||||
pattern_loop_count = effect_param & 0x0F;
|
|
||||||
pattern_loop_channel = channel_idx;
|
|
||||||
}
|
|
||||||
if( pattern_loop_channel == channel_idx ) {
|
|
||||||
/* Loop in progress on this channel. Next iteration. */
|
|
||||||
if( pattern_loop_count == 0 ) {
|
|
||||||
/* Loop finished. */
|
|
||||||
/* Invalidate current marker. */
|
|
||||||
channels[ channel_idx ].pattern_loop_row = current_row + 1;
|
|
||||||
} else {
|
|
||||||
/* Count must be higher than zero. */
|
|
||||||
/* Loop and cancel any breaks on this row. */
|
|
||||||
next_row = channels[ channel_idx ].pattern_loop_row;
|
|
||||||
next_sequence_index = current_sequence_index;
|
|
||||||
}
|
|
||||||
pattern_loop_count -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0xE0:
|
|
||||||
/* Pattern delay.*/
|
|
||||||
tick_counter += ticks_per_row * ( effect_param & 0x0F );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x0F:
|
|
||||||
/* Set Speed/Tempo.*/
|
|
||||||
if( effect_param < 32 ) {
|
|
||||||
set_speed( effect_param );
|
|
||||||
tick_counter = ticks_per_row;
|
|
||||||
} else {
|
|
||||||
set_tempo( effect_param );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x25:
|
|
||||||
/* S3M Set Speed.*/
|
|
||||||
set_speed( effect_param );
|
|
||||||
tick_counter = ticks_per_row;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return song_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_global_volume( int volume ) {
|
|
||||||
if( volume < 0 ) {
|
|
||||||
volume = 0;
|
|
||||||
}
|
|
||||||
if( volume > 64 ) {
|
|
||||||
volume = 64;
|
|
||||||
}
|
|
||||||
global_volume[ 0 ] = volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_speed( int speed ) {
|
|
||||||
if( speed > 0 && speed < 256 ) {
|
|
||||||
ticks_per_row = speed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_tempo( int bpm ) {
|
|
||||||
if( bpm > 31 && bpm < 256 ) {
|
|
||||||
tick_length_samples = ( sampling_rate * 5 ) / ( bpm * 2 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void volume_ramp() {
|
|
||||||
int ramp_idx, next_idx, ramp_end;
|
|
||||||
int volume_ramp_delta, volume, sample;
|
|
||||||
sample = 0;
|
|
||||||
volume_ramp_delta = FP_ONE / volume_ramp_length;
|
|
||||||
volume = 0;
|
|
||||||
ramp_idx = 0;
|
|
||||||
next_idx = 2 * tick_length_samples;
|
|
||||||
ramp_end = volume_ramp_length * 2 - 1;
|
|
||||||
while( ramp_idx <= ramp_end ) {
|
|
||||||
sample = volume_ramp_buffer[ ramp_idx ] * ( FP_ONE - volume ) >> FP_SHIFT;
|
|
||||||
mixing_buffer[ ramp_idx ] = sample + ( mixing_buffer[ ramp_idx ] * volume >> FP_SHIFT );
|
|
||||||
volume_ramp_buffer[ ramp_idx ] = mixing_buffer[ next_idx + ramp_idx ];
|
|
||||||
sample = volume_ramp_buffer[ ramp_idx + 1 ] * ( FP_ONE - volume ) >> FP_SHIFT;
|
|
||||||
mixing_buffer[ ramp_idx + 1 ] = sample + ( mixing_buffer[ ramp_idx + 1 ] * volume >> FP_SHIFT );
|
|
||||||
volume_ramp_buffer[ ramp_idx + 1 ] = mixing_buffer[ next_idx + ramp_idx + 1 ];
|
|
||||||
volume += volume_ramp_delta;
|
|
||||||
ramp_idx += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clear_vol_ramp_buffer() {
|
|
||||||
int ramp_idx, ramp_end;
|
|
||||||
ramp_idx = 0;
|
|
||||||
ramp_end = volume_ramp_length * 2 - 1;
|
|
||||||
while( ramp_idx <= ramp_end ) {
|
|
||||||
volume_ramp_buffer[ ramp_idx ] = 0;
|
|
||||||
ramp_idx += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
|
|
||||||
package ibxm;
|
|
||||||
|
|
||||||
public class Instrument {
|
|
||||||
public String name;
|
|
||||||
public int vibrato_type, vibrato_sweep;
|
|
||||||
public int vibrato_depth, vibrato_rate;
|
|
||||||
public boolean volume_envelope_active, panning_envelope_active;
|
|
||||||
public int volume_fade_out;
|
|
||||||
|
|
||||||
private Envelope volume_envelope, panning_envelope;
|
|
||||||
private int[] key_to_sample;
|
|
||||||
private Sample[] samples;
|
|
||||||
|
|
||||||
public Instrument() {
|
|
||||||
name = "";
|
|
||||||
set_volume_envelope( new Envelope() );
|
|
||||||
set_panning_envelope( new Envelope() );
|
|
||||||
key_to_sample = new int[ 96 ];
|
|
||||||
set_num_samples( 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
public Envelope get_volume_envelope() {
|
|
||||||
return volume_envelope;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_volume_envelope( Envelope envelope ) {
|
|
||||||
if( envelope != null ) {
|
|
||||||
volume_envelope = envelope;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Envelope get_panning_envelope() {
|
|
||||||
return panning_envelope;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_panning_envelope( Envelope envelope ) {
|
|
||||||
if( envelope != null ) {
|
|
||||||
panning_envelope = envelope;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Sample get_sample_from_key( int key ) {
|
|
||||||
int sample_idx;
|
|
||||||
sample_idx = 0;
|
|
||||||
if( key > 0 && key <= key_to_sample.length ) {
|
|
||||||
sample_idx = key_to_sample[ key - 1 ];
|
|
||||||
}
|
|
||||||
return get_sample( sample_idx );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_key_to_sample( int key, int sample ) {
|
|
||||||
if( key > 0 && key <= key_to_sample.length ) {
|
|
||||||
key_to_sample[ key - 1 ] = sample;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int get_num_samples() {
|
|
||||||
return samples.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_num_samples( int num_samples ) {
|
|
||||||
if( num_samples < 1 ) {
|
|
||||||
num_samples = 1;
|
|
||||||
}
|
|
||||||
samples = new Sample[ num_samples ];
|
|
||||||
set_sample( 0, null );
|
|
||||||
}
|
|
||||||
|
|
||||||
public Sample get_sample( int sample_index ) {
|
|
||||||
Sample sample;
|
|
||||||
sample = null;
|
|
||||||
if( sample_index >= 0 && sample_index < samples.length ) {
|
|
||||||
sample = samples[ sample_index ];
|
|
||||||
}
|
|
||||||
if( sample == null ) {
|
|
||||||
sample = samples[ 0 ];
|
|
||||||
}
|
|
||||||
return sample;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_sample( int sample_index, Sample sample ) {
|
|
||||||
if( sample_index >= 0 && sample_index < samples.length ) {
|
|
||||||
samples[ sample_index ] = sample;
|
|
||||||
}
|
|
||||||
if( samples[ 0 ] == null ) {
|
|
||||||
samples[ 0 ] = new Sample();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
|
|
||||||
package ibxm;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Base-2 Log and Exp functions, using linear-interpolated tables.
|
|
||||||
*/
|
|
||||||
public class LogTable {
|
|
||||||
private static final int TABLE_SHIFT = 7; // 128 points (+1 for interp)
|
|
||||||
private static final int INTERP_SHIFT = IBXM.FP_SHIFT - TABLE_SHIFT;
|
|
||||||
private static final int INTERP_MASK = ( 1 << INTERP_SHIFT ) - 1;
|
|
||||||
|
|
||||||
private static final int[] exp_2_table = {
|
|
||||||
32768, 32945, 33124, 33304, 33485, 33667, 33850, 34033,
|
|
||||||
34218, 34404, 34591, 34779, 34968, 35157, 35348, 35540,
|
|
||||||
35733, 35927, 36122, 36319, 36516, 36714, 36913, 37114,
|
|
||||||
37315, 37518, 37722, 37926, 38132, 38339, 38548, 38757,
|
|
||||||
38967, 39179, 39392, 39606, 39821, 40037, 40254, 40473,
|
|
||||||
40693, 40914, 41136, 41359, 41584, 41810, 42037, 42265,
|
|
||||||
42494, 42725, 42957, 43190, 43425, 43661, 43898, 44136,
|
|
||||||
44376, 44617, 44859, 45103, 45347, 45594, 45841, 46090,
|
|
||||||
46340, 46592, 46845, 47099, 47355, 47612, 47871, 48131,
|
|
||||||
48392, 48655, 48919, 49185, 49452, 49720, 49990, 50262,
|
|
||||||
50535, 50809, 51085, 51362, 51641, 51922, 52204, 52487,
|
|
||||||
52772, 53059, 53347, 53636, 53928, 54220, 54515, 54811,
|
|
||||||
55108, 55408, 55709, 56011, 56315, 56621, 56928, 57238,
|
|
||||||
57548, 57861, 58175, 58491, 58809, 59128, 59449, 59772,
|
|
||||||
60096, 60423, 60751, 61081, 61412, 61746, 62081, 62418,
|
|
||||||
62757, 63098, 63440, 63785, 64131, 64479, 64830, 65182,
|
|
||||||
65536
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final int[] log_2_table = {
|
|
||||||
0, 367, 732, 1095, 1454, 1811, 2165, 2517,
|
|
||||||
2865, 3212, 3556, 3897, 4236, 4572, 4906, 5238,
|
|
||||||
5568, 5895, 6220, 6542, 6863, 7181, 7497, 7812,
|
|
||||||
8124, 8434, 8742, 9048, 9352, 9654, 9954, 10252,
|
|
||||||
10548, 10843, 11136, 11427, 11716, 12003, 12289, 12573,
|
|
||||||
12855, 13136, 13414, 13692, 13967, 14241, 14514, 14785,
|
|
||||||
15054, 15322, 15588, 15853, 16117, 16378, 16639, 16898,
|
|
||||||
17156, 17412, 17667, 17920, 18172, 18423, 18673, 18921,
|
|
||||||
19168, 19413, 19657, 19900, 20142, 20383, 20622, 20860,
|
|
||||||
21097, 21333, 21568, 21801, 22034, 22265, 22495, 22724,
|
|
||||||
22952, 23178, 23404, 23628, 23852, 24074, 24296, 24516,
|
|
||||||
24736, 24954, 25171, 25388, 25603, 25817, 26031, 26243,
|
|
||||||
26455, 26665, 26875, 27084, 27292, 27499, 27705, 27910,
|
|
||||||
28114, 28317, 28520, 28721, 28922, 29122, 29321, 29519,
|
|
||||||
29716, 29913, 30109, 30304, 30498, 30691, 30884, 31076,
|
|
||||||
31267, 31457, 31646, 31835, 32023, 32210, 32397, 32582,
|
|
||||||
32768
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Calculate log-base-2 of x (non-fixed-point).
|
|
||||||
A fixed point value is returned.
|
|
||||||
*/
|
|
||||||
public static int log_2( int x ) {
|
|
||||||
int shift;
|
|
||||||
/* Scale x to range 1.0 <= x < 2.0 */
|
|
||||||
shift = IBXM.FP_SHIFT;
|
|
||||||
while( x < IBXM.FP_ONE ) {
|
|
||||||
x <<= 1;
|
|
||||||
shift--;
|
|
||||||
}
|
|
||||||
while( x >= ( IBXM.FP_ONE << 1 ) ) {
|
|
||||||
x >>= 1;
|
|
||||||
shift++;
|
|
||||||
}
|
|
||||||
return ( IBXM.FP_ONE * shift ) + eval_table( log_2_table, x - IBXM.FP_ONE );
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Raise 2 to the power x (fixed point).
|
|
||||||
A fixed point value is returned.
|
|
||||||
*/
|
|
||||||
public static int raise_2( int x ) {
|
|
||||||
int y;
|
|
||||||
y = eval_table( exp_2_table, x & IBXM.FP_MASK ) << IBXM.FP_SHIFT;
|
|
||||||
return y >> IBXM.FP_SHIFT - ( x >> IBXM.FP_SHIFT );
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int eval_table( int[] table, int x ) {
|
|
||||||
int table_idx, table_frac, c, m, y;
|
|
||||||
table_idx = x >> INTERP_SHIFT;
|
|
||||||
table_frac = x & INTERP_MASK;
|
|
||||||
c = table[ table_idx ];
|
|
||||||
m = table[ table_idx + 1 ] - c;
|
|
||||||
y = ( m * table_frac >> INTERP_SHIFT ) + c;
|
|
||||||
return y >> 15 - IBXM.FP_SHIFT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,138 +0,0 @@
|
||||||
|
|
||||||
package ibxm;
|
|
||||||
|
|
||||||
public class Module {
|
|
||||||
public String song_title;
|
|
||||||
public boolean linear_periods, fast_volume_slides, pal;
|
|
||||||
public int global_volume, channel_gain;
|
|
||||||
public int default_speed, default_tempo;
|
|
||||||
public int restart_sequence_index;
|
|
||||||
|
|
||||||
private int[] initial_panning, sequence;
|
|
||||||
private Pattern[] patterns;
|
|
||||||
private Instrument[] instruments;
|
|
||||||
|
|
||||||
private Pattern default_pattern;
|
|
||||||
private Instrument default_instrument;
|
|
||||||
|
|
||||||
public Module() {
|
|
||||||
song_title = IBXM.VERSION;
|
|
||||||
set_num_channels( 1 );
|
|
||||||
set_sequence_length( 1 );
|
|
||||||
set_num_patterns( 0 );
|
|
||||||
set_num_instruments( 0 );
|
|
||||||
default_pattern = new Pattern();
|
|
||||||
default_instrument = new Instrument();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int get_num_channels() {
|
|
||||||
return initial_panning.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_num_channels( int num_channels ) {
|
|
||||||
if( num_channels < 1 ) {
|
|
||||||
num_channels = 1;
|
|
||||||
}
|
|
||||||
initial_panning = new int[ num_channels ];
|
|
||||||
}
|
|
||||||
|
|
||||||
public int get_initial_panning( int channel ) {
|
|
||||||
int panning;
|
|
||||||
panning = 128;
|
|
||||||
if( channel >= 0 && channel < initial_panning.length ) {
|
|
||||||
panning = initial_panning[ channel ];
|
|
||||||
}
|
|
||||||
return panning;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_initial_panning( int channel, int panning ) {
|
|
||||||
if( channel >= 0 && channel < initial_panning.length ) {
|
|
||||||
initial_panning[ channel ] = panning;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int get_sequence_length() {
|
|
||||||
return sequence.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_sequence_length( int sequence_length ) {
|
|
||||||
if( sequence_length < 0 ) {
|
|
||||||
sequence_length = 0;
|
|
||||||
}
|
|
||||||
sequence = new int[ sequence_length ];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_sequence( int sequence_index, int pattern_index ) {
|
|
||||||
if( sequence_index >= 0 && sequence_index < sequence.length ) {
|
|
||||||
sequence[ sequence_index ] = pattern_index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int get_num_patterns() {
|
|
||||||
return patterns.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_num_patterns( int num_patterns ) {
|
|
||||||
if( num_patterns < 0 ) {
|
|
||||||
num_patterns = 0;
|
|
||||||
}
|
|
||||||
patterns = new Pattern[ num_patterns ];
|
|
||||||
}
|
|
||||||
|
|
||||||
public Pattern get_pattern_from_sequence( int sequence_index ) {
|
|
||||||
Pattern pattern;
|
|
||||||
pattern = default_pattern;
|
|
||||||
if( sequence_index >= 0 && sequence_index < sequence.length ) {
|
|
||||||
pattern = get_pattern( sequence[ sequence_index ] );
|
|
||||||
}
|
|
||||||
return pattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Pattern get_pattern( int pattern_index ) {
|
|
||||||
Pattern pattern;
|
|
||||||
pattern = null;
|
|
||||||
if( pattern_index >= 0 && pattern_index < patterns.length ) {
|
|
||||||
pattern = patterns[ pattern_index ];
|
|
||||||
}
|
|
||||||
if( pattern == null ) {
|
|
||||||
pattern = default_pattern;
|
|
||||||
}
|
|
||||||
return pattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_pattern( int pattern_index, Pattern pattern ) {
|
|
||||||
if( pattern_index >= 0 && pattern_index < patterns.length ) {
|
|
||||||
patterns[ pattern_index ] = pattern;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int get_num_instruments() {
|
|
||||||
return instruments.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_num_instruments( int num_instruments ) {
|
|
||||||
if( num_instruments < 0 ) {
|
|
||||||
num_instruments = 0;
|
|
||||||
}
|
|
||||||
instruments = new Instrument[ num_instruments ];
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instrument get_instrument( int instrument_index ) {
|
|
||||||
Instrument instrument;
|
|
||||||
instrument = null;
|
|
||||||
if( instrument_index > 0 && instrument_index <= instruments.length ) {
|
|
||||||
instrument = instruments[ instrument_index - 1 ];
|
|
||||||
}
|
|
||||||
if( instrument == null ) {
|
|
||||||
instrument = default_instrument;
|
|
||||||
}
|
|
||||||
return instrument;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_instrument( int instrument_index, Instrument instrument ) {
|
|
||||||
if( instrument_index > 0 && instrument_index <= instruments.length ) {
|
|
||||||
instruments[ instrument_index - 1 ] = instrument;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
|
|
||||||
package ibxm;
|
|
||||||
|
|
||||||
public class Pattern {
|
|
||||||
public int num_rows;
|
|
||||||
|
|
||||||
private int data_offset, note_index;
|
|
||||||
private byte[] pattern_data;
|
|
||||||
|
|
||||||
public Pattern() {
|
|
||||||
num_rows = 1;
|
|
||||||
set_pattern_data( new byte[ 0 ] );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_pattern_data( byte[] data ) {
|
|
||||||
if( data != null ) {
|
|
||||||
pattern_data = data;
|
|
||||||
}
|
|
||||||
data_offset = 0;
|
|
||||||
note_index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void get_note( int[] note, int index ) {
|
|
||||||
if( index < note_index ) {
|
|
||||||
note_index = 0;
|
|
||||||
data_offset = 0;
|
|
||||||
}
|
|
||||||
while( note_index <= index ) {
|
|
||||||
data_offset = next_note( data_offset, note );
|
|
||||||
note_index += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int next_note( int data_offset, int[] note ) {
|
|
||||||
int bitmask, field;
|
|
||||||
if( data_offset < 0 ) {
|
|
||||||
data_offset = pattern_data.length;
|
|
||||||
}
|
|
||||||
bitmask = 0x80;
|
|
||||||
if( data_offset < pattern_data.length ) {
|
|
||||||
bitmask = pattern_data[ data_offset ] & 0xFF;
|
|
||||||
}
|
|
||||||
if( ( bitmask & 0x80 ) == 0x80 ) {
|
|
||||||
data_offset += 1;
|
|
||||||
} else {
|
|
||||||
bitmask = 0x1F;
|
|
||||||
}
|
|
||||||
for( field = 0; field < 5; field++ ) {
|
|
||||||
note[ field ] = 0;
|
|
||||||
if( ( bitmask & 0x01 ) == 0x01 ) {
|
|
||||||
if( data_offset < pattern_data.length ) {
|
|
||||||
note[ field ] = pattern_data[ data_offset ] & 0xFF;
|
|
||||||
data_offset += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bitmask = bitmask >> 1;
|
|
||||||
}
|
|
||||||
return data_offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,133 +0,0 @@
|
||||||
|
|
||||||
package ibxm;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import javax.sound.sampled.*;
|
|
||||||
|
|
||||||
public class Player {
|
|
||||||
private Thread play_thread;
|
|
||||||
private IBXM ibxm;
|
|
||||||
private Module module;
|
|
||||||
private int song_duration, play_position;
|
|
||||||
private boolean running, loop;
|
|
||||||
private byte[] output_buffer;
|
|
||||||
private SourceDataLine output_line;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Simple command-line test player.
|
|
||||||
*/
|
|
||||||
public static void main( String[] args ) throws Exception {
|
|
||||||
if( args.length < 1 ) {
|
|
||||||
System.err.println( "Usage: java ibxm.Player <module file>" );
|
|
||||||
System.exit( 0 );
|
|
||||||
}
|
|
||||||
FileInputStream file_input_stream = new FileInputStream( args[ 0 ] );
|
|
||||||
Player player = new Player();
|
|
||||||
player.set_module( Player.load_module( file_input_stream ) );
|
|
||||||
file_input_stream.close();
|
|
||||||
player.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Decode the data in the specified InputStream into a Module instance.
|
|
||||||
@param input an InputStream containing the module file to be decoded.
|
|
||||||
@throws IllegalArgumentException if the data is not recognised as a module file.
|
|
||||||
*/
|
|
||||||
public static Module load_module( InputStream input ) throws IllegalArgumentException, IOException {
|
|
||||||
DataInputStream data_input_stream = new DataInputStream( input );
|
|
||||||
/* Check if data is in XM format.*/
|
|
||||||
byte[] xm_header = new byte[ 60 ];
|
|
||||||
data_input_stream.readFully( xm_header );
|
|
||||||
if( FastTracker2.is_xm( xm_header ) )
|
|
||||||
return FastTracker2.load_xm( xm_header, data_input_stream );
|
|
||||||
/* Check if data is in ScreamTracker 3 format.*/
|
|
||||||
byte[] s3m_header = new byte[ 96 ];
|
|
||||||
System.arraycopy( xm_header, 0, s3m_header, 0, 60 );
|
|
||||||
data_input_stream.readFully( s3m_header, 60, 36 );
|
|
||||||
if( ScreamTracker3.is_s3m( s3m_header ) )
|
|
||||||
return ScreamTracker3.load_s3m( s3m_header, data_input_stream );
|
|
||||||
/* Check if data is in ProTracker format.*/
|
|
||||||
byte[] mod_header = new byte[ 1084 ];
|
|
||||||
System.arraycopy( s3m_header, 0, mod_header, 0, 96 );
|
|
||||||
data_input_stream.readFully( mod_header, 96, 988 );
|
|
||||||
return ProTracker.load_mod( mod_header, data_input_stream );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Instantiate a new Player.
|
|
||||||
*/
|
|
||||||
public Player() throws LineUnavailableException {
|
|
||||||
ibxm = new IBXM( 48000 );
|
|
||||||
set_loop( true );
|
|
||||||
output_line = AudioSystem.getSourceDataLine( new AudioFormat( 48000, 16, 2, true, true ) );
|
|
||||||
output_buffer = new byte[ 1024 * 4 ];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Set the Module instance to be played.
|
|
||||||
*/
|
|
||||||
public void set_module( Module m ) {
|
|
||||||
if( m != null ) module = m;
|
|
||||||
stop();
|
|
||||||
ibxm.set_module( module );
|
|
||||||
song_duration = ibxm.calculate_song_duration();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
If loop is true, playback will continue indefinitely,
|
|
||||||
otherwise the module will play through once and stop.
|
|
||||||
*/
|
|
||||||
public void set_loop( boolean loop ) {
|
|
||||||
this.loop = loop;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Open the audio device and begin playback.
|
|
||||||
If a module is already playing it will be restarted.
|
|
||||||
*/
|
|
||||||
public void play() {
|
|
||||||
stop();
|
|
||||||
play_thread = new Thread( new Driver() );
|
|
||||||
play_thread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Stop playback and close the audio device.
|
|
||||||
*/
|
|
||||||
public void stop() {
|
|
||||||
running = false;
|
|
||||||
if( play_thread != null ) {
|
|
||||||
try {
|
|
||||||
play_thread.join();
|
|
||||||
} catch( InterruptedException ie ) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Driver implements Runnable {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if( running ) return;
|
|
||||||
try {
|
|
||||||
output_line.open();
|
|
||||||
output_line.start();
|
|
||||||
play_position = 0;
|
|
||||||
running = true;
|
|
||||||
while( running ) {
|
|
||||||
int frames = song_duration - play_position;
|
|
||||||
if( frames > 1024 ) frames = 1024;
|
|
||||||
ibxm.get_audio( output_buffer, frames );
|
|
||||||
output_line.write( output_buffer, 0, frames * 4 );
|
|
||||||
play_position += frames;
|
|
||||||
if( play_position >= song_duration ) {
|
|
||||||
play_position = 0;
|
|
||||||
if( !loop ) running = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output_line.drain();
|
|
||||||
output_line.close();
|
|
||||||
} catch( LineUnavailableException lue ) {
|
|
||||||
lue.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,234 +0,0 @@
|
||||||
package ibxm;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
public class ProTracker {
|
|
||||||
public static boolean is_mod( byte[] header_1084_bytes ) {
|
|
||||||
boolean is_mod;
|
|
||||||
is_mod = false;
|
|
||||||
if( calculate_num_channels( header_1084_bytes ) > 0 ) {
|
|
||||||
is_mod = true;
|
|
||||||
}
|
|
||||||
return is_mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Module load_mod( byte[] header_1084_bytes, DataInput data_input ) throws IOException {
|
|
||||||
int num_channels, channel_idx, panning;
|
|
||||||
int sequence_length, restart_idx, sequence_idx;
|
|
||||||
int num_patterns, pattern_idx, instrument_idx;
|
|
||||||
Module module;
|
|
||||||
num_channels = calculate_num_channels( header_1084_bytes );
|
|
||||||
if( num_channels < 1 ) {
|
|
||||||
throw new IllegalArgumentException( "ProTracker: Unrecognised module format!" );
|
|
||||||
}
|
|
||||||
module = new Module();
|
|
||||||
module.song_title = ascii_text( header_1084_bytes, 0, 20 );
|
|
||||||
module.pal = ( num_channels == 4 );
|
|
||||||
module.global_volume = 64;
|
|
||||||
module.channel_gain = IBXM.FP_ONE * 3 / 8;
|
|
||||||
module.default_speed = 6;
|
|
||||||
module.default_tempo = 125;
|
|
||||||
module.set_num_channels( num_channels );
|
|
||||||
for( channel_idx = 0; channel_idx < num_channels; channel_idx++ ) {
|
|
||||||
panning = 64;
|
|
||||||
if( ( channel_idx & 0x03 ) == 0x01 || ( channel_idx & 0x03 ) == 0x02 ) {
|
|
||||||
panning = 192;
|
|
||||||
}
|
|
||||||
module.set_initial_panning( channel_idx, panning );
|
|
||||||
}
|
|
||||||
sequence_length = header_1084_bytes[ 950 ] & 0x7F;
|
|
||||||
restart_idx = header_1084_bytes[ 951 ] & 0x7F;
|
|
||||||
if( restart_idx >= sequence_length ) {
|
|
||||||
restart_idx = 0;
|
|
||||||
}
|
|
||||||
module.restart_sequence_index = restart_idx;
|
|
||||||
module.set_sequence_length( sequence_length );
|
|
||||||
for( sequence_idx = 0; sequence_idx < sequence_length; sequence_idx++ ) {
|
|
||||||
module.set_sequence( sequence_idx, header_1084_bytes[ 952 + sequence_idx ] & 0x7F );
|
|
||||||
}
|
|
||||||
num_patterns = calculate_num_patterns( header_1084_bytes );
|
|
||||||
module.set_num_patterns( num_patterns );
|
|
||||||
for( pattern_idx = 0; pattern_idx < num_patterns; pattern_idx++ ) {
|
|
||||||
module.set_pattern( pattern_idx, read_mod_pattern( data_input, num_channels ) );
|
|
||||||
}
|
|
||||||
module.set_num_instruments( 31 );
|
|
||||||
for( instrument_idx = 1; instrument_idx <= 31; instrument_idx++ ) {
|
|
||||||
module.set_instrument( instrument_idx, read_mod_instrument( header_1084_bytes, instrument_idx, data_input ) );
|
|
||||||
}
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int calculate_num_patterns( byte[] module_header ) {
|
|
||||||
int num_patterns, order_entry, pattern_idx;
|
|
||||||
num_patterns = 0;
|
|
||||||
for( pattern_idx = 0; pattern_idx < 128; pattern_idx++ ) {
|
|
||||||
order_entry = module_header[ 952 + pattern_idx ] & 0x7F;
|
|
||||||
if( order_entry >= num_patterns ) {
|
|
||||||
num_patterns = order_entry + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return num_patterns;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int calculate_num_channels( byte[] module_header ) {
|
|
||||||
int num_channels;
|
|
||||||
switch( ( module_header[ 1082 ] << 8 ) | module_header[ 1083 ] ) {
|
|
||||||
case 0x4b2e: /* M.K. */
|
|
||||||
case 0x4b21: /* M!K! */
|
|
||||||
case 0x542e: /* N.T. */
|
|
||||||
case 0x5434: /* FLT4 */
|
|
||||||
num_channels = 4;
|
|
||||||
break;
|
|
||||||
case 0x484e: /* xCHN */
|
|
||||||
num_channels = module_header[ 1080 ] - 48;
|
|
||||||
break;
|
|
||||||
case 0x4348: /* xxCH */
|
|
||||||
num_channels = ( ( module_header[ 1080 ] - 48 ) * 10 ) + ( module_header[ 1081 ] - 48 );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
/* Not recognised. */
|
|
||||||
num_channels = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return num_channels;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Pattern read_mod_pattern( DataInput data_input, int num_channels ) throws IOException {
|
|
||||||
int input_idx, output_idx;
|
|
||||||
int period, instrument, effect, effect_param;
|
|
||||||
Pattern pattern;
|
|
||||||
byte[] input_pattern_data, output_pattern_data;
|
|
||||||
pattern = new Pattern();
|
|
||||||
pattern.num_rows = 64;
|
|
||||||
input_pattern_data = new byte[ 64 * num_channels * 4 ];
|
|
||||||
output_pattern_data = new byte[ 64 * num_channels * 5 ];
|
|
||||||
data_input.readFully( input_pattern_data );
|
|
||||||
input_idx = 0;
|
|
||||||
output_idx = 0;
|
|
||||||
while( input_idx < input_pattern_data.length ) {
|
|
||||||
period = ( input_pattern_data[ input_idx ] & 0x0F ) << 8;
|
|
||||||
period = period | ( input_pattern_data[ input_idx + 1 ] & 0xFF );
|
|
||||||
output_pattern_data[ output_idx ] = to_key( period );
|
|
||||||
instrument = input_pattern_data[ input_idx ] & 0x10;
|
|
||||||
instrument = instrument | ( ( input_pattern_data[ input_idx + 2 ] & 0xF0 ) >> 4 );
|
|
||||||
output_pattern_data[ output_idx + 1 ] = ( byte ) instrument;
|
|
||||||
effect = input_pattern_data[ input_idx + 2 ] & 0x0F;
|
|
||||||
effect_param = input_pattern_data[ input_idx + 3 ] & 0xFF;
|
|
||||||
if( effect == 0x01 && effect_param == 0 ) {
|
|
||||||
/* Portamento up of zero has no effect. */
|
|
||||||
effect = 0;
|
|
||||||
}
|
|
||||||
if( effect == 0x02 && effect_param == 0 ) {
|
|
||||||
/* Portamento down of zero has no effect. */
|
|
||||||
effect = 0;
|
|
||||||
}
|
|
||||||
if( effect == 0x08 && num_channels == 4 ) {
|
|
||||||
/* Some Amiga mods use effect 0x08 for reasons other than panning.*/
|
|
||||||
effect = 0;
|
|
||||||
effect_param = 0;
|
|
||||||
}
|
|
||||||
if( effect == 0x0A && effect_param == 0 ) {
|
|
||||||
/* Volume slide of zero has no effect.*/
|
|
||||||
effect = 0;
|
|
||||||
}
|
|
||||||
if( effect == 0x05 && effect_param == 0 ) {
|
|
||||||
/* Porta + Volume slide of zero has no effect.*/
|
|
||||||
effect = 0x03;
|
|
||||||
}
|
|
||||||
if( effect == 0x06 && effect_param == 0 ) {
|
|
||||||
/* Vibrato + Volume slide of zero has no effect.*/
|
|
||||||
effect = 0x04;
|
|
||||||
}
|
|
||||||
output_pattern_data[ output_idx + 3 ] = ( byte ) effect;
|
|
||||||
output_pattern_data[ output_idx + 4 ] = ( byte ) effect_param;
|
|
||||||
input_idx += 4;
|
|
||||||
output_idx += 5;
|
|
||||||
}
|
|
||||||
pattern.set_pattern_data( output_pattern_data );
|
|
||||||
return pattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Instrument read_mod_instrument( byte[] mod_header, int idx, DataInput data_input ) throws IOException {
|
|
||||||
int header_offset, sample_data_length;
|
|
||||||
int loop_start, loop_length, sample_idx, fine_tune;
|
|
||||||
Instrument instrument;
|
|
||||||
Sample sample;
|
|
||||||
byte[] raw_sample_data;
|
|
||||||
short[] sample_data;
|
|
||||||
header_offset = ( idx - 1 ) * 30 + 20;
|
|
||||||
instrument = new Instrument();
|
|
||||||
instrument.name = ascii_text( mod_header, header_offset, 22 );
|
|
||||||
sample = new Sample();
|
|
||||||
sample_data_length = unsigned_short_be( mod_header, header_offset + 22 ) << 1;
|
|
||||||
fine_tune = mod_header[ header_offset + 24 ] & 0x0F;
|
|
||||||
if( fine_tune > 7 ) {
|
|
||||||
fine_tune -= 16;
|
|
||||||
}
|
|
||||||
sample.transpose = ( fine_tune << IBXM.FP_SHIFT ) / 96;
|
|
||||||
sample.volume = mod_header[ header_offset + 25 ] & 0x7F;
|
|
||||||
loop_start = unsigned_short_be( mod_header, header_offset + 26 ) << 1;
|
|
||||||
loop_length = unsigned_short_be( mod_header, header_offset + 28 ) << 1;
|
|
||||||
if( loop_length < 4 ) {
|
|
||||||
loop_length = 0;
|
|
||||||
}
|
|
||||||
raw_sample_data = new byte[ sample_data_length ];
|
|
||||||
sample_data = new short[ sample_data_length ];
|
|
||||||
try {
|
|
||||||
data_input.readFully( raw_sample_data );
|
|
||||||
} catch( EOFException e ) {
|
|
||||||
System.out.println( "ProTracker: Instrument " + idx + " has samples missing." );
|
|
||||||
}
|
|
||||||
for( sample_idx = 0; sample_idx < raw_sample_data.length; sample_idx++ ) {
|
|
||||||
sample_data[ sample_idx ] = ( short ) ( raw_sample_data[ sample_idx ] << 8 );
|
|
||||||
}
|
|
||||||
sample.set_sample_data( sample_data, loop_start, loop_length, false );
|
|
||||||
instrument.set_num_samples( 1 );
|
|
||||||
instrument.set_sample( 0, sample );
|
|
||||||
return instrument;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte to_key( int period ) {
|
|
||||||
int oct, key;
|
|
||||||
if( period < 32 ) {
|
|
||||||
key = 0;
|
|
||||||
} else {
|
|
||||||
oct = LogTable.log_2( 7256 ) - LogTable.log_2( period );
|
|
||||||
if( oct < 0 ) {
|
|
||||||
key = 0;
|
|
||||||
} else {
|
|
||||||
key = oct * 12;
|
|
||||||
key = key >> ( IBXM.FP_SHIFT - 1 );
|
|
||||||
key = ( key >> 1 ) + ( key & 1 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ( byte ) key;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int unsigned_short_be( byte[] buf, int offset ) {
|
|
||||||
int value;
|
|
||||||
value = ( buf[ offset ] & 0xFF ) << 8;
|
|
||||||
value = value | ( buf[ offset + 1 ] & 0xFF );
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String ascii_text( byte[] buffer, int offset, int length ) {
|
|
||||||
int idx, chr;
|
|
||||||
byte[] string_buffer;
|
|
||||||
String string;
|
|
||||||
string_buffer = new byte[ length ];
|
|
||||||
for( idx = 0; idx < length; idx++ ) {
|
|
||||||
chr = buffer[ offset + idx ];
|
|
||||||
if( chr < 32 ) {
|
|
||||||
chr = 32;
|
|
||||||
}
|
|
||||||
string_buffer[ idx ] = ( byte ) chr;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
string = new String( string_buffer, 0, length, "ISO-8859-1" );
|
|
||||||
} catch( UnsupportedEncodingException e ) {
|
|
||||||
string = "";
|
|
||||||
}
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,239 +0,0 @@
|
||||||
|
|
||||||
package ibxm;
|
|
||||||
|
|
||||||
public class Sample {
|
|
||||||
public String name;
|
|
||||||
public boolean set_panning;
|
|
||||||
public int volume, panning;
|
|
||||||
public int transpose;
|
|
||||||
|
|
||||||
private int loop_start, loop_length;
|
|
||||||
private short[] sample_data;
|
|
||||||
|
|
||||||
/* For the sinc interpolator.*/
|
|
||||||
private static final int POINT_SHIFT = 4;
|
|
||||||
private static final int POINTS = 1 << POINT_SHIFT;
|
|
||||||
private static final int OVERLAP = POINTS >> 1;
|
|
||||||
private static final int INTERP_SHIFT = IBXM.FP_SHIFT - 4;
|
|
||||||
private static final int INTERP_BITMASK = ( 1 << INTERP_SHIFT ) - 1;
|
|
||||||
private static final short[] sinc_table = {
|
|
||||||
0, -7, 27, -71, 142, -227, 299, 32439, 299, -227, 142, -71, 27, -7, 0, 0,
|
|
||||||
0, 0, -5, 36, -142, 450, -1439, 32224, 2302, -974, 455, -190, 64, -15, 2, 0,
|
|
||||||
0, 6, -33, 128, -391, 1042, -2894, 31584, 4540, -1765, 786, -318, 105, -25, 3, 0,
|
|
||||||
0, 10, -55, 204, -597, 1533, -4056, 30535, 6977, -2573, 1121, -449, 148, -36, 5, 0,
|
|
||||||
-1, 13, -71, 261, -757, 1916, -4922, 29105, 9568, -3366, 1448, -578, 191, -47, 7, 0,
|
|
||||||
-1, 15, -81, 300, -870, 2185, -5498, 27328, 12263, -4109, 1749, -698, 232, -58, 9, 0,
|
|
||||||
-1, 15, -86, 322, -936, 2343, -5800, 25249, 15006, -4765, 2011, -802, 269, -68, 10, 0,
|
|
||||||
-1, 15, -87, 328, -957, 2394, -5849, 22920, 17738, -5298, 2215, -885, 299, -77, 12, 0,
|
|
||||||
0, 14, -83, 319, -938, 2347, -5671, 20396, 20396, -5671, 2347, -938, 319, -83, 14, 0,
|
|
||||||
0, 12, -77, 299, -885, 2215, -5298, 17738, 22920, -5849, 2394, -957, 328, -87, 15, -1,
|
|
||||||
0, 10, -68, 269, -802, 2011, -4765, 15006, 25249, -5800, 2343, -936, 322, -86, 15, -1,
|
|
||||||
0, 9, -58, 232, -698, 1749, -4109, 12263, 27328, -5498, 2185, -870, 300, -81, 15, -1,
|
|
||||||
0, 7, -47, 191, -578, 1448, -3366, 9568, 29105, -4922, 1916, -757, 261, -71, 13, -1,
|
|
||||||
0, 5, -36, 148, -449, 1121, -2573, 6977, 30535, -4056, 1533, -597, 204, -55, 10, 0,
|
|
||||||
0, 3, -25, 105, -318, 786, -1765, 4540, 31584, -2894, 1042, -391, 128, -33, 6, 0,
|
|
||||||
0, 2, -15, 64, -190, 455, -974, 2302, 32224, -1439, 450, -142, 36, -5, 0, 0,
|
|
||||||
0, 0, -7, 27, -71, 142, -227, 299, 32439, 299, -227, 142, -71, 27, -7, 0
|
|
||||||
};
|
|
||||||
|
|
||||||
public Sample() {
|
|
||||||
name = "";
|
|
||||||
set_sample_data( new short[ 0 ], 0, 0, false );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_sample_data( short[] data, int loop_start, int loop_length, boolean ping_pong ) {
|
|
||||||
int offset;
|
|
||||||
short sample;
|
|
||||||
if( loop_start < 0 ) {
|
|
||||||
loop_start = 0;
|
|
||||||
}
|
|
||||||
if( loop_start >= data.length ) {
|
|
||||||
loop_start = data.length - 1;
|
|
||||||
}
|
|
||||||
if( loop_start + loop_length > data.length ) {
|
|
||||||
loop_length = data.length - loop_start;
|
|
||||||
}
|
|
||||||
if( loop_length <= 1 ) {
|
|
||||||
sample_data = new short[ OVERLAP + data.length + OVERLAP * 3 ];
|
|
||||||
System.arraycopy( data, 0, sample_data, OVERLAP, data.length );
|
|
||||||
offset = 0;
|
|
||||||
while( offset < OVERLAP ) {
|
|
||||||
sample = sample_data[ OVERLAP + data.length - 1 ];
|
|
||||||
sample = ( short ) ( sample * ( OVERLAP - offset ) / OVERLAP );
|
|
||||||
sample_data[ OVERLAP + data.length + offset ] = sample;
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
loop_start = OVERLAP + data.length + OVERLAP;
|
|
||||||
loop_length = 1;
|
|
||||||
} else {
|
|
||||||
if( ping_pong ) {
|
|
||||||
sample_data = new short[ OVERLAP + loop_start + loop_length * 2 + OVERLAP * 2 ];
|
|
||||||
System.arraycopy( data, 0, sample_data, OVERLAP, loop_start + loop_length );
|
|
||||||
offset = 0;
|
|
||||||
while( offset < loop_length ) {
|
|
||||||
sample = data[ loop_start + loop_length - offset - 1 ];
|
|
||||||
sample_data[ OVERLAP + loop_start + loop_length + offset ] = sample;
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
loop_start = loop_start + OVERLAP;
|
|
||||||
loop_length = loop_length * 2;
|
|
||||||
} else {
|
|
||||||
sample_data = new short[ OVERLAP + loop_start + loop_length + OVERLAP * 2 ];
|
|
||||||
System.arraycopy( data, 0, sample_data, OVERLAP, loop_start + loop_length );
|
|
||||||
loop_start = loop_start + OVERLAP;
|
|
||||||
}
|
|
||||||
offset = 0;
|
|
||||||
while( offset < OVERLAP * 2 ) {
|
|
||||||
sample = sample_data[ loop_start + offset ];
|
|
||||||
sample_data[ loop_start + loop_length + offset ] = sample;
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.loop_start = loop_start;
|
|
||||||
this.loop_length = loop_length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resample_nearest(
|
|
||||||
int sample_idx, int sample_frac, int step, int left_gain, int right_gain,
|
|
||||||
int[] mix_buffer, int frame_offset, int frames ) {
|
|
||||||
int loop_end, offset, end, max_sample_idx;
|
|
||||||
sample_idx += OVERLAP;
|
|
||||||
loop_end = loop_start + loop_length - 1;
|
|
||||||
offset = frame_offset << 1;
|
|
||||||
end = ( frame_offset + frames - 1 ) << 1;
|
|
||||||
while( frames > 0 ) {
|
|
||||||
if( sample_idx > loop_end ) {
|
|
||||||
if( loop_length <= 1 ) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sample_idx = loop_start + ( sample_idx - loop_start ) % loop_length;
|
|
||||||
}
|
|
||||||
max_sample_idx = sample_idx + ( ( sample_frac + ( frames - 1 ) * step ) >> IBXM.FP_SHIFT );
|
|
||||||
if( max_sample_idx > loop_end ) {
|
|
||||||
while( sample_idx <= loop_end ) {
|
|
||||||
mix_buffer[ offset++ ] += sample_data[ sample_idx ] * left_gain >> IBXM.FP_SHIFT;
|
|
||||||
mix_buffer[ offset++ ] += sample_data[ sample_idx ] * right_gain >> IBXM.FP_SHIFT;
|
|
||||||
sample_frac += step;
|
|
||||||
sample_idx += sample_frac >> IBXM.FP_SHIFT;
|
|
||||||
sample_frac &= IBXM.FP_MASK;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
while( offset <= end ) {
|
|
||||||
mix_buffer[ offset++ ] += sample_data[ sample_idx ] * left_gain >> IBXM.FP_SHIFT;
|
|
||||||
mix_buffer[ offset++ ] += sample_data[ sample_idx ] * right_gain >> IBXM.FP_SHIFT;
|
|
||||||
sample_frac += step;
|
|
||||||
sample_idx += sample_frac >> IBXM.FP_SHIFT;
|
|
||||||
sample_frac &= IBXM.FP_MASK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frames = ( end - offset + 2 ) >> 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resample_linear(
|
|
||||||
int sample_idx, int sample_frac, int step, int left_gain, int right_gain,
|
|
||||||
int[] mix_buffer, int frame_offset, int frames ) {
|
|
||||||
int loop_end, offset, end, max_sample_idx, amplitude;
|
|
||||||
sample_idx += OVERLAP;
|
|
||||||
loop_end = loop_start + loop_length - 1;
|
|
||||||
offset = frame_offset << 1;
|
|
||||||
end = ( frame_offset + frames - 1 ) << 1;
|
|
||||||
while( frames > 0 ) {
|
|
||||||
if( sample_idx > loop_end ) {
|
|
||||||
if( loop_length <= 1 ) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sample_idx = loop_start + ( sample_idx - loop_start ) % loop_length;
|
|
||||||
}
|
|
||||||
max_sample_idx = sample_idx + ( ( sample_frac + ( frames - 1 ) * step ) >> IBXM.FP_SHIFT );
|
|
||||||
if( max_sample_idx > loop_end ) {
|
|
||||||
while( sample_idx <= loop_end ) {
|
|
||||||
amplitude = sample_data[ sample_idx ];
|
|
||||||
amplitude += ( sample_data[ sample_idx + 1 ] - amplitude ) * sample_frac >> IBXM.FP_SHIFT;
|
|
||||||
mix_buffer[ offset++ ] += amplitude * left_gain >> IBXM.FP_SHIFT;
|
|
||||||
mix_buffer[ offset++ ] += amplitude * right_gain >> IBXM.FP_SHIFT;
|
|
||||||
sample_frac += step;
|
|
||||||
sample_idx += sample_frac >> IBXM.FP_SHIFT;
|
|
||||||
sample_frac &= IBXM.FP_MASK;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
while( offset <= end ) {
|
|
||||||
amplitude = sample_data[ sample_idx ];
|
|
||||||
amplitude += ( sample_data[ sample_idx + 1 ] - amplitude ) * sample_frac >> IBXM.FP_SHIFT;
|
|
||||||
mix_buffer[ offset++ ] += amplitude * left_gain >> IBXM.FP_SHIFT;
|
|
||||||
mix_buffer[ offset++ ] += amplitude * right_gain >> IBXM.FP_SHIFT;
|
|
||||||
sample_frac += step;
|
|
||||||
sample_idx += sample_frac >> IBXM.FP_SHIFT;
|
|
||||||
sample_frac &= IBXM.FP_MASK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frames = ( end - offset + 2 ) >> 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resample_sinc(
|
|
||||||
int sample_idx, int sample_frac, int step, int left_gain, int right_gain,
|
|
||||||
int[] mix_buffer, int frame_offset, int frames ) {
|
|
||||||
int offset, end, loop_end, table_idx, a1, a2, amplitude;
|
|
||||||
loop_end = loop_start + loop_length - 1;
|
|
||||||
offset = frame_offset << 1;
|
|
||||||
end = ( frame_offset + frames - 1 ) << 1;
|
|
||||||
while( offset <= end ) {
|
|
||||||
if( sample_idx > loop_end ) {
|
|
||||||
if( loop_length <= 1 ) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sample_idx = loop_start + ( sample_idx - loop_start ) % loop_length;
|
|
||||||
}
|
|
||||||
table_idx = ( sample_frac >> INTERP_SHIFT ) << POINT_SHIFT;
|
|
||||||
a1 = sinc_table[ table_idx + 0 ] * sample_data[ sample_idx + 0 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 1 ] * sample_data[ sample_idx + 1 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 2 ] * sample_data[ sample_idx + 2 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 3 ] * sample_data[ sample_idx + 3 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 4 ] * sample_data[ sample_idx + 4 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 5 ] * sample_data[ sample_idx + 5 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 6 ] * sample_data[ sample_idx + 6 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 7 ] * sample_data[ sample_idx + 7 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 8 ] * sample_data[ sample_idx + 8 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 9 ] * sample_data[ sample_idx + 9 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 10 ] * sample_data[ sample_idx + 10 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 11 ] * sample_data[ sample_idx + 11 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 12 ] * sample_data[ sample_idx + 12 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 13 ] * sample_data[ sample_idx + 13 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 14 ] * sample_data[ sample_idx + 14 ] >> 15;
|
|
||||||
a1 += sinc_table[ table_idx + 15 ] * sample_data[ sample_idx + 15 ] >> 15;
|
|
||||||
a2 = sinc_table[ table_idx + 16 ] * sample_data[ sample_idx + 0 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 17 ] * sample_data[ sample_idx + 1 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 18 ] * sample_data[ sample_idx + 2 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 19 ] * sample_data[ sample_idx + 3 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 20 ] * sample_data[ sample_idx + 4 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 21 ] * sample_data[ sample_idx + 5 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 22 ] * sample_data[ sample_idx + 6 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 23 ] * sample_data[ sample_idx + 7 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 24 ] * sample_data[ sample_idx + 8 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 25 ] * sample_data[ sample_idx + 9 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 26 ] * sample_data[ sample_idx + 10 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 27 ] * sample_data[ sample_idx + 11 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 28 ] * sample_data[ sample_idx + 12 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 29 ] * sample_data[ sample_idx + 13 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 30 ] * sample_data[ sample_idx + 14 ] >> 15;
|
|
||||||
a2 += sinc_table[ table_idx + 31 ] * sample_data[ sample_idx + 15 ] >> 15;
|
|
||||||
amplitude = a1 + ( ( a2 - a1 ) * ( sample_frac & INTERP_BITMASK ) >> INTERP_SHIFT );
|
|
||||||
mix_buffer[ offset ] += amplitude * left_gain >> IBXM.FP_SHIFT;
|
|
||||||
mix_buffer[ offset + 1 ] += amplitude * right_gain >> IBXM.FP_SHIFT;
|
|
||||||
offset += 2;
|
|
||||||
sample_frac += step;
|
|
||||||
sample_idx += sample_frac >> IBXM.FP_SHIFT;
|
|
||||||
sample_frac &= IBXM.FP_MASK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean has_finished( int sample_idx ) {
|
|
||||||
boolean finished;
|
|
||||||
finished = false;
|
|
||||||
if( loop_length <= 1 && sample_idx > loop_start ) {
|
|
||||||
finished = true;
|
|
||||||
}
|
|
||||||
return finished;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,490 +0,0 @@
|
||||||
|
|
||||||
package ibxm;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
public class ScreamTracker3 {
|
|
||||||
private static final int[] effect_map = new int[] {
|
|
||||||
0xFF,
|
|
||||||
0x25, /* A: Set Speed.*/
|
|
||||||
0x0B, /* B: Pattern Jump.*/
|
|
||||||
0x0D, /* C: Pattern Break.*/
|
|
||||||
0x0A, /* D: Volume Slide.*/
|
|
||||||
0x02, /* E: Portamento Down.*/
|
|
||||||
0x01, /* F: Portamento Up.*/
|
|
||||||
0x03, /* G: Tone Portamento.*/
|
|
||||||
0x04, /* H: Vibrato.*/
|
|
||||||
0x1D, /* I: Tremor.*/
|
|
||||||
0x00, /* J: Arpeggio.*/
|
|
||||||
0x06, /* K: Vibrato + Volume Slide.*/
|
|
||||||
0x05, /* L: Tone Portamento + Volume Slide.*/
|
|
||||||
0xFF, /* M: */
|
|
||||||
0xFF, /* N: */
|
|
||||||
0x09, /* O: Sample Offset.*/
|
|
||||||
0xFF, /* P: */
|
|
||||||
0x1B, /* Q: Retrig + Volume Slide.*/
|
|
||||||
0x07, /* R: Tremolo.*/
|
|
||||||
0x0E, /* S: Extended Effects.*/
|
|
||||||
0x0F, /* T: Set Tempo.*/
|
|
||||||
0x24, /* U: Fine Vibrato.*/
|
|
||||||
0x10, /* V: Set Global Volume. */
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final int[] effect_s_map = new int[] {
|
|
||||||
0x00, /* 0: Set Filter.*/
|
|
||||||
0x03, /* 1: Glissando.*/
|
|
||||||
0x05, /* 2: Set Fine Tune.*/
|
|
||||||
0x04, /* 3: Set Vibrato Waveform.*/
|
|
||||||
0x07, /* 4: Set Tremolo Waveform.*/
|
|
||||||
0xFF, /* 5: */
|
|
||||||
0xFF, /* 6: */
|
|
||||||
0xFF, /* 7: */
|
|
||||||
0x08, /* 8: Set Panning.*/
|
|
||||||
0xFF, /* 9: */
|
|
||||||
0x09, /* A: Stereo Control.*/
|
|
||||||
0x06, /* B: Pattern Loop.*/
|
|
||||||
0x0C, /* C: Note Cut.*/
|
|
||||||
0x0D, /* D: Note Delay.*/
|
|
||||||
0x0E, /* E: Pattern Delay.*/
|
|
||||||
0x0F /* F: Invert Loop.*/
|
|
||||||
};
|
|
||||||
|
|
||||||
public static boolean is_s3m( byte[] header_96_bytes ) {
|
|
||||||
String s3m_identifier;
|
|
||||||
s3m_identifier = ascii_text( header_96_bytes, 44, 4 );
|
|
||||||
return s3m_identifier.equals( "SCRM" );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Module load_s3m( byte[] header_96_bytes, DataInput data_input ) throws IOException {
|
|
||||||
int num_pattern_orders, num_instruments, num_patterns, num_channels;
|
|
||||||
@SuppressWarnings("unused") //Forge
|
|
||||||
int flags, tracker_version, master_volume, panning, channel_config, sequence_length;
|
|
||||||
int instrument_idx, pattern_idx, channel_idx, order_idx, panning_offset;
|
|
||||||
boolean signed_samples, stereo_mode, default_panning;
|
|
||||||
int[] channel_map, sequence;
|
|
||||||
byte[] s3m_file;
|
|
||||||
Module module;
|
|
||||||
Instrument instrument;
|
|
||||||
s3m_file = read_s3m_file( header_96_bytes, data_input );
|
|
||||||
module = new Module();
|
|
||||||
module.song_title = ascii_text( s3m_file, 0, 28 );
|
|
||||||
num_pattern_orders = get_num_pattern_orders( s3m_file );
|
|
||||||
num_instruments = get_num_instruments( s3m_file );
|
|
||||||
num_patterns = get_num_patterns( s3m_file );
|
|
||||||
flags = unsigned_short_le( s3m_file, 38 );
|
|
||||||
tracker_version = unsigned_short_le( s3m_file, 40 );
|
|
||||||
if( ( flags & 0x40 ) == 0x40 || tracker_version == 0x1300 ) {
|
|
||||||
module.fast_volume_slides = true;
|
|
||||||
}
|
|
||||||
signed_samples = false;
|
|
||||||
if( unsigned_short_le( s3m_file, 42 ) == 0x01 ) {
|
|
||||||
signed_samples = true;
|
|
||||||
}
|
|
||||||
module.global_volume = s3m_file[ 48 ] & 0xFF;
|
|
||||||
module.default_speed = s3m_file[ 49 ] & 0xFF;
|
|
||||||
module.default_tempo = s3m_file[ 50 ] & 0xFF;
|
|
||||||
master_volume = s3m_file[ 51 ] & 0x7F;
|
|
||||||
module.channel_gain = ( master_volume << IBXM.FP_SHIFT ) >> 7;
|
|
||||||
stereo_mode = ( s3m_file[ 51 ] & 0x80 ) == 0x80;
|
|
||||||
default_panning = ( s3m_file[ 53 ] & 0xFF ) == 0xFC;
|
|
||||||
channel_map = new int[ 32 ];
|
|
||||||
num_channels = 0;
|
|
||||||
for( channel_idx = 0; channel_idx < 32; channel_idx++ ) {
|
|
||||||
channel_config = s3m_file[ 64 + channel_idx ] & 0xFF;
|
|
||||||
channel_map[ channel_idx ] = -1;
|
|
||||||
if( channel_config < 16 ) {
|
|
||||||
channel_map[ channel_idx ] = num_channels;
|
|
||||||
num_channels += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.set_num_channels( num_channels );
|
|
||||||
panning_offset = 96 + num_pattern_orders + num_instruments * 2 + num_patterns * 2;
|
|
||||||
for( channel_idx = 0; channel_idx < 32; channel_idx++ ) {
|
|
||||||
if( channel_map[ channel_idx ] < 0 ) continue;
|
|
||||||
panning = 7;
|
|
||||||
if( stereo_mode ) {
|
|
||||||
panning = 12;
|
|
||||||
if( ( s3m_file[ 64 + channel_idx ] & 0xFF ) < 8 ) {
|
|
||||||
panning = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if( default_panning ) {
|
|
||||||
flags = s3m_file[ panning_offset + channel_idx ] & 0xFF;
|
|
||||||
if( ( flags & 0x20 ) == 0x20 ) {
|
|
||||||
panning = flags & 0xF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.set_initial_panning( channel_map[ channel_idx ], panning * 17 );
|
|
||||||
}
|
|
||||||
sequence = read_s3m_sequence( s3m_file );
|
|
||||||
module.set_sequence_length( sequence.length );
|
|
||||||
for( order_idx = 0; order_idx < sequence.length; order_idx++ ) {
|
|
||||||
module.set_sequence( order_idx, sequence[ order_idx ] );
|
|
||||||
}
|
|
||||||
module.set_num_instruments( num_instruments );
|
|
||||||
for( instrument_idx = 0; instrument_idx < num_instruments; instrument_idx++ ) {
|
|
||||||
instrument = read_s3m_instrument( s3m_file, instrument_idx, signed_samples );
|
|
||||||
module.set_instrument( instrument_idx + 1, instrument );
|
|
||||||
}
|
|
||||||
module.set_num_patterns( num_patterns );
|
|
||||||
for( pattern_idx = 0; pattern_idx < num_patterns; pattern_idx++ ) {
|
|
||||||
module.set_pattern( pattern_idx, read_s3m_pattern( s3m_file, pattern_idx, channel_map ) );
|
|
||||||
}
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int[] read_s3m_sequence( byte[] s3m_file ) {
|
|
||||||
int num_pattern_orders, sequence_length;
|
|
||||||
int sequence_idx, order_idx, pattern_order;
|
|
||||||
int[] sequence;
|
|
||||||
num_pattern_orders = get_num_pattern_orders( s3m_file );
|
|
||||||
sequence_length = 0;
|
|
||||||
for( order_idx = 0; order_idx < num_pattern_orders; order_idx++ ) {
|
|
||||||
pattern_order = s3m_file[ 96 + order_idx ] & 0xFF;
|
|
||||||
if( pattern_order == 255 ) {
|
|
||||||
break;
|
|
||||||
} else if( pattern_order < 254 ) {
|
|
||||||
sequence_length += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sequence = new int[ sequence_length ];
|
|
||||||
sequence_idx = 0;
|
|
||||||
for( order_idx = 0; order_idx < num_pattern_orders; order_idx++ ) {
|
|
||||||
pattern_order = s3m_file[ 96 + order_idx ] & 0xFF;
|
|
||||||
if( pattern_order == 255 ) {
|
|
||||||
break;
|
|
||||||
} else if( pattern_order < 254 ) {
|
|
||||||
sequence[ sequence_idx ] = pattern_order;
|
|
||||||
sequence_idx += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Instrument read_s3m_instrument( byte[] s3m_file, int instrument_idx, boolean signed_samples ) {
|
|
||||||
int instrument_offset;
|
|
||||||
int sample_data_offset, sample_data_length;
|
|
||||||
int loop_start, loop_length, c2_rate, sample_idx, amplitude;
|
|
||||||
boolean sixteen_bit;
|
|
||||||
Instrument instrument;
|
|
||||||
Sample sample;
|
|
||||||
short[] sample_data;
|
|
||||||
instrument_offset = get_instrument_offset( s3m_file, instrument_idx );
|
|
||||||
instrument = new Instrument();
|
|
||||||
instrument.name = ascii_text( s3m_file, instrument_offset + 48, 28 );
|
|
||||||
sample = new Sample();
|
|
||||||
if( s3m_file[ instrument_offset ] == 1 ) {
|
|
||||||
sample_data_length = get_sample_data_length( s3m_file, instrument_offset );
|
|
||||||
loop_start = unsigned_short_le( s3m_file, instrument_offset + 20 );
|
|
||||||
loop_length = unsigned_short_le( s3m_file, instrument_offset + 24 ) - loop_start;
|
|
||||||
sample.volume = s3m_file[ instrument_offset + 28 ] & 0xFF;
|
|
||||||
if( s3m_file[ instrument_offset + 30 ] != 0 ) {
|
|
||||||
throw new IllegalArgumentException( "ScreamTracker3: Packed samples not supported!" );
|
|
||||||
}
|
|
||||||
if( ( s3m_file[ instrument_offset + 31 ] & 0x01 ) == 0 ) {
|
|
||||||
loop_length = 0;
|
|
||||||
}
|
|
||||||
if( ( s3m_file[ instrument_offset + 31 ] & 0x02 ) != 0 ) {
|
|
||||||
throw new IllegalArgumentException( "ScreamTracker3: Stereo samples not supported!" );
|
|
||||||
}
|
|
||||||
sixteen_bit = ( s3m_file[ instrument_offset + 31 ] & 0x04 ) != 0;
|
|
||||||
c2_rate = unsigned_short_le( s3m_file, instrument_offset + 32 );
|
|
||||||
sample.transpose = LogTable.log_2( c2_rate ) - LogTable.log_2( 8363 );
|
|
||||||
sample_data_offset = get_sample_data_offset( s3m_file, instrument_offset );
|
|
||||||
if( sixteen_bit ) {
|
|
||||||
if( signed_samples ) {
|
|
||||||
throw new IllegalArgumentException( "ScreamTracker3: Signed 16-bit samples not supported!" );
|
|
||||||
}
|
|
||||||
sample_data_length >>= 1;
|
|
||||||
sample_data = new short[ sample_data_length ];
|
|
||||||
for( sample_idx = 0; sample_idx < sample_data_length; sample_idx++ ) {
|
|
||||||
amplitude = s3m_file[ sample_data_offset + sample_idx * 2 ] & 0xFF;
|
|
||||||
amplitude |= ( s3m_file[ sample_data_offset + sample_idx * 2 + 1 ] & 0xFF ) << 8;
|
|
||||||
sample_data[ sample_idx ] = ( short ) ( amplitude - 32768 );
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sample_data = new short[ sample_data_length ];
|
|
||||||
if( signed_samples ) {
|
|
||||||
for( sample_idx = 0; sample_idx < sample_data_length; sample_idx++ ) {
|
|
||||||
amplitude = s3m_file[ sample_data_offset + sample_idx ] << 8;
|
|
||||||
sample_data[ sample_idx ] = ( short ) amplitude;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for( sample_idx = 0; sample_idx < sample_data_length; sample_idx++ ) {
|
|
||||||
amplitude = ( s3m_file[ sample_data_offset + sample_idx ] & 0xFF ) << 8;
|
|
||||||
sample_data[ sample_idx ] = ( short ) ( amplitude - 32768 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sample.set_sample_data( sample_data, loop_start, loop_length, false );
|
|
||||||
}
|
|
||||||
instrument.set_num_samples( 1 );
|
|
||||||
instrument.set_sample( 0, sample );
|
|
||||||
return instrument;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Pattern read_s3m_pattern( byte[] s3m_file, int pattern_idx, int[] channel_map ) {
|
|
||||||
int pattern_offset;
|
|
||||||
int num_channels, num_notes;
|
|
||||||
int row_idx, channel_idx, note_idx;
|
|
||||||
int token, key, volume_column, effect, effect_param;
|
|
||||||
byte[] pattern_data;
|
|
||||||
Pattern pattern;
|
|
||||||
num_channels = 0;
|
|
||||||
for( channel_idx = 0; channel_idx < 32; channel_idx++ ) {
|
|
||||||
if( channel_map[ channel_idx ] >= num_channels ) {
|
|
||||||
num_channels = channel_idx + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
num_notes = num_channels * 64;
|
|
||||||
pattern_data = new byte[ num_notes * 5 ];
|
|
||||||
row_idx = 0;
|
|
||||||
pattern_offset = get_pattern_offset( s3m_file, pattern_idx ) + 2;
|
|
||||||
while( row_idx < 64 ) {
|
|
||||||
token = s3m_file[ pattern_offset ] & 0xFF;
|
|
||||||
pattern_offset += 1;
|
|
||||||
if( token > 0 ) {
|
|
||||||
channel_idx = channel_map[ token & 0x1F ];
|
|
||||||
note_idx = ( num_channels * row_idx + channel_idx ) * 5;
|
|
||||||
if( ( token & 0x20 ) == 0x20 ) {
|
|
||||||
/* Key + Instrument.*/
|
|
||||||
if( channel_idx >= 0 ) {
|
|
||||||
key = s3m_file[ pattern_offset ] & 0xFF;
|
|
||||||
if( key == 255 ) {
|
|
||||||
key = 0;
|
|
||||||
} else if( key == 254 ) {
|
|
||||||
key = 97;
|
|
||||||
} else {
|
|
||||||
key = ( ( key & 0xF0 ) >> 4 ) * 12 + ( key & 0x0F ) + 1;
|
|
||||||
while( key > 96 ) {
|
|
||||||
key = key - 12;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pattern_data[ note_idx ] = ( byte ) key;
|
|
||||||
pattern_data[ note_idx + 1 ] = s3m_file[ pattern_offset + 1 ];
|
|
||||||
}
|
|
||||||
pattern_offset += 2;
|
|
||||||
}
|
|
||||||
if( ( token & 0x40 ) == 0x40 ) {
|
|
||||||
/* Volume.*/
|
|
||||||
if( channel_idx >= 0 ) {
|
|
||||||
volume_column = ( s3m_file[ pattern_offset ] & 0xFF ) + 0x10;
|
|
||||||
pattern_data[ note_idx + 2 ] = ( byte ) volume_column;
|
|
||||||
}
|
|
||||||
pattern_offset += 1;
|
|
||||||
}
|
|
||||||
if( ( token & 0x80 ) == 0x80 ) {
|
|
||||||
/* Effect + Param.*/
|
|
||||||
if( channel_idx >= 0 ) {
|
|
||||||
effect = s3m_file[ pattern_offset ] & 0xFF;
|
|
||||||
effect_param = s3m_file[ pattern_offset + 1 ] & 0xFF;
|
|
||||||
effect = effect_map[ effect & 0x1F ];
|
|
||||||
if( effect == 0xFF ) {
|
|
||||||
effect = 0;
|
|
||||||
effect_param = 0;
|
|
||||||
}
|
|
||||||
if( effect == 0x0E ) {
|
|
||||||
effect = effect_s_map[ ( effect_param & 0xF0 ) >> 4 ];
|
|
||||||
effect_param = effect_param & 0x0F;
|
|
||||||
switch( effect ) {
|
|
||||||
case 0x08:
|
|
||||||
effect = 0x08;
|
|
||||||
effect_param = effect_param * 17;
|
|
||||||
break;
|
|
||||||
case 0x09:
|
|
||||||
effect = 0x08;
|
|
||||||
if( effect_param > 7 ) {
|
|
||||||
effect_param -= 8;
|
|
||||||
} else {
|
|
||||||
effect_param += 8;
|
|
||||||
}
|
|
||||||
effect_param = effect_param * 17;
|
|
||||||
break;
|
|
||||||
case 0xFF:
|
|
||||||
effect = 0;
|
|
||||||
effect_param = 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
effect_param = ( ( effect & 0x0F ) << 4 ) | ( effect_param & 0x0F );
|
|
||||||
effect = 0x0E;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pattern_data[ note_idx + 3 ] = ( byte ) effect;
|
|
||||||
pattern_data[ note_idx + 4 ] = ( byte ) effect_param;
|
|
||||||
}
|
|
||||||
pattern_offset += 2;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
row_idx += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pattern = new Pattern();
|
|
||||||
pattern.num_rows = 64;
|
|
||||||
pattern.set_pattern_data( pattern_data );
|
|
||||||
return pattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] read_s3m_file( byte[] header_96_bytes, DataInput data_input ) throws IOException {
|
|
||||||
int s3m_file_length;
|
|
||||||
int num_pattern_orders, num_instruments, num_patterns;
|
|
||||||
int instrument_idx, pattern_idx;
|
|
||||||
int instrument_offset, sample_data_offset, pattern_offset;
|
|
||||||
byte[] s3m_file;
|
|
||||||
if( !is_s3m( header_96_bytes ) ) {
|
|
||||||
throw new IllegalArgumentException( "ScreamTracker3: Not an S3M file!" );
|
|
||||||
}
|
|
||||||
s3m_file = header_96_bytes;
|
|
||||||
s3m_file_length = header_96_bytes.length;
|
|
||||||
num_pattern_orders = get_num_pattern_orders( s3m_file );
|
|
||||||
num_instruments = get_num_instruments( s3m_file );
|
|
||||||
num_patterns = get_num_patterns( s3m_file );
|
|
||||||
s3m_file_length += num_pattern_orders;
|
|
||||||
s3m_file_length += num_instruments * 2;
|
|
||||||
s3m_file_length += num_patterns * 2;
|
|
||||||
/* Read enough of file to calculate the length.*/
|
|
||||||
s3m_file = read_more( s3m_file, s3m_file_length, data_input );
|
|
||||||
for( instrument_idx = 0; instrument_idx < num_instruments; instrument_idx++ ) {
|
|
||||||
instrument_offset = get_instrument_offset( s3m_file, instrument_idx );
|
|
||||||
instrument_offset += 80;
|
|
||||||
if( instrument_offset > s3m_file_length ) {
|
|
||||||
s3m_file_length = instrument_offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for( pattern_idx = 0; pattern_idx < num_patterns; pattern_idx++ ) {
|
|
||||||
pattern_offset = get_pattern_offset( s3m_file, pattern_idx );
|
|
||||||
pattern_offset += 2;
|
|
||||||
if( pattern_offset > s3m_file_length ) {
|
|
||||||
s3m_file_length = pattern_offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s3m_file = read_more( s3m_file, s3m_file_length, data_input );
|
|
||||||
/* Read rest of file.*/
|
|
||||||
for( instrument_idx = 0; instrument_idx < num_instruments; instrument_idx++ ) {
|
|
||||||
instrument_offset = get_instrument_offset( s3m_file, instrument_idx );
|
|
||||||
sample_data_offset = get_sample_data_offset( s3m_file, instrument_offset );
|
|
||||||
sample_data_offset += get_sample_data_length( s3m_file, instrument_offset );
|
|
||||||
if( sample_data_offset > s3m_file_length ) {
|
|
||||||
s3m_file_length = sample_data_offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for( pattern_idx = 0; pattern_idx < num_patterns; pattern_idx++ ) {
|
|
||||||
pattern_offset = get_pattern_offset( s3m_file, pattern_idx );
|
|
||||||
pattern_offset += get_pattern_length( s3m_file, pattern_offset );
|
|
||||||
pattern_offset += 2;
|
|
||||||
if( pattern_offset > s3m_file_length ) {
|
|
||||||
s3m_file_length = pattern_offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s3m_file = read_more( s3m_file, s3m_file_length, data_input );
|
|
||||||
return s3m_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int get_num_pattern_orders( byte[] s3m_file ) {
|
|
||||||
int num_pattern_orders;
|
|
||||||
num_pattern_orders = unsigned_short_le( s3m_file, 32 );
|
|
||||||
return num_pattern_orders;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int get_num_instruments( byte[] s3m_file ) {
|
|
||||||
int num_instruments;
|
|
||||||
num_instruments = unsigned_short_le( s3m_file, 34 );
|
|
||||||
return num_instruments;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int get_num_patterns( byte[] s3m_file ) {
|
|
||||||
int num_patterns;
|
|
||||||
num_patterns = unsigned_short_le( s3m_file, 36 );
|
|
||||||
return num_patterns;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int get_instrument_offset( byte[] s3m_file, int instrument_idx ) {
|
|
||||||
int instrument_offset, pointer_offset;
|
|
||||||
pointer_offset = 96 + get_num_pattern_orders( s3m_file );
|
|
||||||
instrument_offset = unsigned_short_le( s3m_file, pointer_offset + instrument_idx * 2 ) << 4;
|
|
||||||
return instrument_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int get_sample_data_offset( byte[] s3m_file, int instrument_offset ) {
|
|
||||||
int sample_data_offset;
|
|
||||||
sample_data_offset = 0;
|
|
||||||
if( s3m_file[ instrument_offset ] == 1 ) {
|
|
||||||
sample_data_offset = ( s3m_file[ instrument_offset + 13 ] & 0xFF ) << 20;
|
|
||||||
sample_data_offset |= unsigned_short_le( s3m_file, instrument_offset + 14 ) << 4;
|
|
||||||
}
|
|
||||||
return sample_data_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int get_sample_data_length( byte[] s3m_file, int instrument_offset ) {
|
|
||||||
int sample_data_length;
|
|
||||||
boolean sixteen_bit;
|
|
||||||
sample_data_length = 0;
|
|
||||||
if( s3m_file[ instrument_offset ] == 1 ) {
|
|
||||||
sample_data_length = unsigned_short_le( s3m_file, instrument_offset + 16 );
|
|
||||||
sixteen_bit = ( s3m_file[ instrument_offset + 31 ] & 0x04 ) != 0;
|
|
||||||
if( sixteen_bit ) {
|
|
||||||
sample_data_length <<= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sample_data_length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int get_pattern_offset( byte[] s3m_file, int pattern_idx ) {
|
|
||||||
int pattern_offset, pointer_offset;
|
|
||||||
pointer_offset = 96 + get_num_pattern_orders( s3m_file );
|
|
||||||
pointer_offset += get_num_instruments( s3m_file ) * 2;
|
|
||||||
pattern_offset = unsigned_short_le( s3m_file, pointer_offset + pattern_idx * 2 ) << 4;
|
|
||||||
return pattern_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int get_pattern_length( byte[] s3m_file, int pattern_offset ) {
|
|
||||||
int pattern_length;
|
|
||||||
pattern_length = unsigned_short_le( s3m_file, pattern_offset );
|
|
||||||
return pattern_length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] read_more( byte[] old_data, int new_length, DataInput data_input ) throws IOException {
|
|
||||||
byte[] new_data;
|
|
||||||
new_data = old_data;
|
|
||||||
if( new_length > old_data.length ) {
|
|
||||||
new_data = new byte[ new_length ];
|
|
||||||
System.arraycopy( old_data, 0, new_data, 0, old_data.length );
|
|
||||||
try {
|
|
||||||
data_input.readFully( new_data, old_data.length, new_data.length - old_data.length );
|
|
||||||
} catch( EOFException e ) {
|
|
||||||
System.out.println( "ScreamTracker3: Module has been truncated!" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int unsigned_short_le( byte[] buffer, int offset ) {
|
|
||||||
int value;
|
|
||||||
value = buffer[ offset ] & 0xFF;
|
|
||||||
value = value | ( ( buffer[ offset + 1 ] & 0xFF ) << 8 );
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String ascii_text( byte[] buffer, int offset, int length ) {
|
|
||||||
int idx, chr;
|
|
||||||
byte[] string_buffer;
|
|
||||||
String string;
|
|
||||||
string_buffer = new byte[ length ];
|
|
||||||
for( idx = 0; idx < length; idx++ ) {
|
|
||||||
chr = buffer[ offset + idx ];
|
|
||||||
if( chr < 32 ) {
|
|
||||||
chr = 32;
|
|
||||||
}
|
|
||||||
string_buffer[ idx ] = ( byte ) chr;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
string = new String( string_buffer, 0, length, "ISO-8859-1" );
|
|
||||||
} catch( UnsupportedEncodingException e ) {
|
|
||||||
string = "";
|
|
||||||
}
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
* Minecraft Forge
|
|
||||||
* Copyright (c) 2016-2018.
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation version 2.1
|
|
||||||
* of the License.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.client;
|
|
||||||
|
|
||||||
import net.minecraft.client.audio.MusicTicker;
|
|
||||||
import net.minecraft.util.SoundEvent;
|
|
||||||
import net.minecraft.util.Util.EnumOS;
|
|
||||||
import net.minecraft.world.GameType;
|
|
||||||
import net.minecraft.client.settings.GameSettings.Options;
|
|
||||||
import net.minecraftforge.common.util.EnumHelper;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
public class EnumHelperClient extends EnumHelper
|
|
||||||
{
|
|
||||||
private static Class<?>[][] clientTypes =
|
|
||||||
{
|
|
||||||
{GameType.class, int.class, String.class, String.class},
|
|
||||||
{Options.class, String.class, boolean.class, boolean.class},
|
|
||||||
{EnumOS.class},
|
|
||||||
{MusicTicker.MusicType.class, SoundEvent.class, int.class, int.class}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static GameType addGameType(String name, int id, String displayName, String shortName)
|
|
||||||
{
|
|
||||||
return addEnum(GameType.class, name, id, displayName, shortName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static Options addOptions(String name, String langName, boolean isSlider, boolean isToggle)
|
|
||||||
{
|
|
||||||
return addEnum(Options.class, name, langName, isSlider, isToggle);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static Options addOptions(String name, String langName, boolean isSlider, boolean isToggle, float valMin, float valMax, float valStep)
|
|
||||||
{
|
|
||||||
return addEnum(Options.class, name,
|
|
||||||
new Class<?>[]{String.class, boolean.class, boolean.class, float.class, float.class, float.class},
|
|
||||||
langName, isSlider, isToggle, valMin, valMax, valStep);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static EnumOS addOS2(String name)
|
|
||||||
{
|
|
||||||
return addEnum(EnumOS.class, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static MusicTicker.MusicType addMusicType(String name, SoundEvent sound, int minDelay, int maxDelay)
|
|
||||||
{
|
|
||||||
return addEnum(MusicTicker.MusicType.class, name, sound, minDelay, maxDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static <T extends Enum<? >> T addEnum(Class<T> enumType, String enumName, Object... paramValues)
|
|
||||||
{
|
|
||||||
return addEnum(clientTypes, enumType, enumName, paramValues);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -31,6 +31,7 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.FloatBuffer;
|
import java.nio.FloatBuffer;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.vecmath.Matrix3f;
|
import javax.vecmath.Matrix3f;
|
||||||
|
@ -40,6 +41,7 @@ import javax.vecmath.Vector4f;
|
||||||
|
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.block.Block;
|
||||||
import net.minecraft.block.state.IBlockState;
|
import net.minecraft.block.state.IBlockState;
|
||||||
|
import net.minecraft.client.GameSettings;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.audio.ISound;
|
import net.minecraft.client.audio.ISound;
|
||||||
import net.minecraft.client.audio.SoundHandler;
|
import net.minecraft.client.audio.SoundHandler;
|
||||||
|
@ -48,15 +50,12 @@ import net.minecraft.client.gui.BossInfoClient;
|
||||||
import net.minecraft.client.gui.FontRenderer;
|
import net.minecraft.client.gui.FontRenderer;
|
||||||
import net.minecraft.client.gui.GuiMainMenu;
|
import net.minecraft.client.gui.GuiMainMenu;
|
||||||
import net.minecraft.client.gui.GuiScreen;
|
import net.minecraft.client.gui.GuiScreen;
|
||||||
import net.minecraft.client.gui.ScaledResolution;
|
|
||||||
import net.minecraft.client.model.ModelBiped;
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher;
|
import net.minecraft.client.renderer.BlockRendererDispatcher;
|
||||||
import net.minecraft.client.renderer.BufferBuilder;
|
import net.minecraft.client.renderer.BufferBuilder;
|
||||||
import net.minecraft.client.renderer.EntityRenderer;
|
import net.minecraft.client.renderer.EntityRenderer;
|
||||||
import net.minecraft.client.renderer.GlStateManager;
|
import net.minecraft.client.renderer.GlStateManager;
|
||||||
import net.minecraft.client.renderer.OpenGlHelper;
|
import net.minecraft.client.renderer.OpenGlHelper;
|
||||||
import net.minecraft.client.renderer.RenderGlobal;
|
import net.minecraft.client.renderer.RenderGlobal;
|
||||||
import net.minecraft.client.renderer.RenderItem;
|
|
||||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||||
import net.minecraft.client.renderer.block.model.BlockFaceUV;
|
import net.minecraft.client.renderer.block.model.BlockFaceUV;
|
||||||
import net.minecraft.client.renderer.block.model.IBakedModel;
|
import net.minecraft.client.renderer.block.model.IBakedModel;
|
||||||
|
@ -72,16 +71,13 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||||
import net.minecraft.client.renderer.texture.TextureManager;
|
import net.minecraft.client.renderer.texture.TextureManager;
|
||||||
import net.minecraft.client.renderer.texture.TextureMap;
|
import net.minecraft.client.renderer.texture.TextureMap;
|
||||||
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
|
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
|
||||||
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
|
|
||||||
import net.minecraft.client.renderer.vertex.VertexFormat;
|
import net.minecraft.client.renderer.vertex.VertexFormat;
|
||||||
import net.minecraft.client.renderer.vertex.VertexFormatElement;
|
import net.minecraft.client.renderer.vertex.VertexFormatElement;
|
||||||
import net.minecraft.client.renderer.vertex.VertexFormatElement.EnumUsage;
|
import net.minecraft.client.renderer.vertex.VertexFormatElement.EnumUsage;
|
||||||
import net.minecraft.client.resources.FoliageColorReloadListener;
|
import net.minecraft.client.resources.FoliageColorReloadListener;
|
||||||
import net.minecraft.client.resources.GrassColorReloadListener;
|
import net.minecraft.client.resources.GrassColorReloadListener;
|
||||||
import net.minecraft.client.resources.I18n;
|
import net.minecraft.client.resources.I18n;
|
||||||
import net.minecraft.client.resources.IResourceManagerReloadListener;
|
|
||||||
import net.minecraft.client.resources.LanguageManager;
|
import net.minecraft.client.resources.LanguageManager;
|
||||||
import net.minecraft.client.settings.GameSettings;
|
|
||||||
import net.minecraft.client.util.SearchTreeManager;
|
import net.minecraft.client.util.SearchTreeManager;
|
||||||
import net.minecraft.entity.Entity;
|
import net.minecraft.entity.Entity;
|
||||||
import net.minecraft.entity.EntityLivingBase;
|
import net.minecraft.entity.EntityLivingBase;
|
||||||
|
@ -101,7 +97,6 @@ import net.minecraft.util.math.MathHelper;
|
||||||
import net.minecraft.util.math.RayTraceResult;
|
import net.minecraft.util.math.RayTraceResult;
|
||||||
import net.minecraft.util.registry.IRegistry;
|
import net.minecraft.util.registry.IRegistry;
|
||||||
import net.minecraft.util.text.TextFormatting;
|
import net.minecraft.util.text.TextFormatting;
|
||||||
import net.minecraft.world.IBlockAccess;
|
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
import net.minecraft.world.biome.Biome;
|
import net.minecraft.world.biome.Biome;
|
||||||
import net.minecraftforge.client.event.ColorHandlerEvent;
|
import net.minecraftforge.client.event.ColorHandlerEvent;
|
||||||
|
@ -111,7 +106,6 @@ import net.minecraftforge.client.event.FOVUpdateEvent;
|
||||||
import net.minecraftforge.client.event.GuiScreenEvent;
|
import net.minecraftforge.client.event.GuiScreenEvent;
|
||||||
import net.minecraftforge.client.event.InputUpdateEvent;
|
import net.minecraftforge.client.event.InputUpdateEvent;
|
||||||
import net.minecraftforge.client.event.ModelBakeEvent;
|
import net.minecraftforge.client.event.ModelBakeEvent;
|
||||||
import net.minecraftforge.client.event.MouseEvent;
|
|
||||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||||
import net.minecraftforge.client.event.RenderHandEvent;
|
import net.minecraftforge.client.event.RenderHandEvent;
|
||||||
import net.minecraftforge.client.event.RenderSpecificHandEvent;
|
import net.minecraftforge.client.event.RenderSpecificHandEvent;
|
||||||
|
@ -132,25 +126,17 @@ import net.minecraftforge.common.model.IModelPart;
|
||||||
import net.minecraftforge.common.model.ITransformation;
|
import net.minecraftforge.common.model.ITransformation;
|
||||||
import net.minecraftforge.common.model.TRSRTransformation;
|
import net.minecraftforge.common.model.TRSRTransformation;
|
||||||
import net.minecraftforge.fml.VersionChecker;
|
import net.minecraftforge.fml.VersionChecker;
|
||||||
import net.minecraftforge.fml.client.FMLClientHandler;
|
|
||||||
import net.minecraftforge.fml.client.registry.ClientRegistry;
|
import net.minecraftforge.fml.client.registry.ClientRegistry;
|
||||||
import net.minecraftforge.fml.common.FMLLog;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.lwjgl.BufferUtils;
|
import org.lwjgl.BufferUtils;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
public class ForgeHooksClient
|
public class ForgeHooksClient
|
||||||
{
|
{
|
||||||
//private static final ResourceLocation ITEM_GLINT = new ResourceLocation("textures/misc/enchanted_item_glint.png");
|
//private static final ResourceLocation ITEM_GLINT = new ResourceLocation("textures/misc/enchanted_item_glint.png");
|
||||||
|
|
||||||
static TextureManager engine()
|
|
||||||
{
|
|
||||||
return FMLClientHandler.instance().getClient().renderEngine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getArmorTexture(Entity entity, ItemStack armor, String _default, EntityEquipmentSlot slot, String type)
|
public static String getArmorTexture(Entity entity, ItemStack armor, String _default, EntityEquipmentSlot slot, String type)
|
||||||
{
|
{
|
||||||
String result = armor.getItem().getArmorTexture(armor, entity, slot, type);
|
String result = armor.getItem().getArmorTexture(armor, entity, slot, type);
|
||||||
|
@ -406,18 +392,18 @@ public class ForgeHooksClient
|
||||||
m.setIdentity();
|
m.setIdentity();
|
||||||
m.setTranslation(TRSRTransformation.toVecmath(transform.translation));
|
m.setTranslation(TRSRTransformation.toVecmath(transform.translation));
|
||||||
t.setIdentity();
|
t.setIdentity();
|
||||||
t.rotY(transform.rotation.y);
|
t.rotY(transform.rotation.func_195900_b());
|
||||||
m.mul(t);
|
m.mul(t);
|
||||||
t.setIdentity();
|
t.setIdentity();
|
||||||
t.rotX(transform.rotation.x);
|
t.rotX(transform.rotation.func_195899_a());
|
||||||
m.mul(t);
|
m.mul(t);
|
||||||
t.setIdentity();
|
t.setIdentity();
|
||||||
t.rotZ(transform.rotation.z);
|
t.rotZ(transform.rotation.func_195902_c());
|
||||||
m.mul(t);
|
m.mul(t);
|
||||||
t.setIdentity();
|
t.setIdentity();
|
||||||
t.m00 = transform.scale.x;
|
t.m00 = transform.scale.func_195899_a();
|
||||||
t.m11 = transform.scale.y;
|
t.m11 = transform.scale.func_195900_b();
|
||||||
t.m22 = transform.scale.z;
|
t.m22 = transform.scale.func_195902_c();
|
||||||
m.mul(t);
|
m.mul(t);
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ import net.minecraft.util.ResourceLocation;
|
||||||
import net.minecraft.world.biome.*;
|
import net.minecraft.world.biome.*;
|
||||||
import static net.minecraftforge.common.BiomeDictionary.Type.*;
|
import static net.minecraftforge.common.BiomeDictionary.Type.*;
|
||||||
import net.minecraftforge.fml.common.FMLLog;
|
import net.minecraftforge.fml.common.FMLLog;
|
||||||
import net.minecraftforge.fml.common.registry.ForgeRegistries;
|
import net.minecraftforge.registries.ForgeRegistries;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
|
@ -32,6 +32,11 @@ import java.util.UUID;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.apache.logging.log4j.Marker;
|
||||||
|
import org.apache.logging.log4j.MarkerManager;
|
||||||
|
|
||||||
import net.minecraft.entity.Entity;
|
import net.minecraft.entity.Entity;
|
||||||
import net.minecraft.nbt.CompressedStreamTools;
|
import net.minecraft.nbt.CompressedStreamTools;
|
||||||
import net.minecraft.nbt.NBTTagCompound;
|
import net.minecraft.nbt.NBTTagCompound;
|
||||||
|
@ -50,9 +55,6 @@ import net.minecraftforge.common.config.Configuration;
|
||||||
import net.minecraftforge.common.config.Property;
|
import net.minecraftforge.common.config.Property;
|
||||||
import net.minecraftforge.common.util.Constants;
|
import net.minecraftforge.common.util.Constants;
|
||||||
import net.minecraftforge.fml.server.ServerLifecycleHooks;
|
import net.minecraftforge.fml.server.ServerLifecycleHooks;
|
||||||
import net.minecraftforge.fml.common.FMLCommonHandler;
|
|
||||||
import net.minecraftforge.fml.common.FMLLog;
|
|
||||||
import net.minecraftforge.fml.common.Loader;
|
|
||||||
import net.minecraftforge.fml.ModContainer;
|
import net.minecraftforge.fml.ModContainer;
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
|
@ -87,11 +89,11 @@ import com.google.common.collect.Sets;
|
||||||
* The chunkloading is configurable at runtime. The file "config/forgeChunkLoading.cfg" contains both default configuration for chunkloading, and a sample individual mod
|
* The chunkloading is configurable at runtime. The file "config/forgeChunkLoading.cfg" contains both default configuration for chunkloading, and a sample individual mod
|
||||||
* specific override section.
|
* specific override section.
|
||||||
*
|
*
|
||||||
* @author cpw
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class ForgeChunkManager
|
public class ForgeChunkManager
|
||||||
{
|
{
|
||||||
|
public static Marker CHUNK_MANAGER = MarkerManager.getMarker("CHUNKMANAGER");
|
||||||
|
private static Logger LOGGER = LogManager.getLogger();
|
||||||
private static int defaultMaxCount;
|
private static int defaultMaxCount;
|
||||||
private static int defaultMaxChunks;
|
private static int defaultMaxChunks;
|
||||||
private static boolean overridesEnabled;
|
private static boolean overridesEnabled;
|
||||||
|
@ -143,7 +145,7 @@ public class ForgeChunkManager
|
||||||
final ImmutableSetMultimap<ChunkPos, Ticket> persistentChunksFor = getPersistentChunksFor(world);
|
final ImmutableSetMultimap<ChunkPos, Ticket> persistentChunksFor = getPersistentChunksFor(world);
|
||||||
final ImmutableSet.Builder<Chunk> builder = ImmutableSet.builder();
|
final ImmutableSet.Builder<Chunk> builder = ImmutableSet.builder();
|
||||||
world.profiler.startSection("forcedChunkLoading");
|
world.profiler.startSection("forcedChunkLoading");
|
||||||
builder.addAll(persistentChunksFor.keys().stream().filter(Objects::nonNull).map(input -> world.getChunkFromChunkCoords(input.x, input.z)).iterator());
|
builder.addAll(persistentChunksFor.keys().stream().filter(Objects::nonNull).map(input -> world.getChunk(input.x, input.z)).iterator());
|
||||||
world.profiler.endStartSection("regularChunkLoading");
|
world.profiler.endStartSection("regularChunkLoading");
|
||||||
builder.addAll(chunkIterator);
|
builder.addAll(chunkIterator);
|
||||||
world.profiler.endSection();
|
world.profiler.endSection();
|
||||||
|
@ -265,7 +267,7 @@ public class ForgeChunkManager
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FMLLog.log.error("Attempt to create a player ticket without a valid player");
|
LOGGER.error(CHUNK_MANAGER, "Attempt to create a player ticket without a valid player");
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -279,7 +281,7 @@ public class ForgeChunkManager
|
||||||
{
|
{
|
||||||
if (depth > getMaxChunkDepthFor(modId) || (depth <= 0 && getMaxChunkDepthFor(modId) > 0))
|
if (depth > getMaxChunkDepthFor(modId) || (depth <= 0 && getMaxChunkDepthFor(modId) > 0))
|
||||||
{
|
{
|
||||||
FMLLog.log.warn("The mod {} tried to modify the chunk ticket depth to: {}, its allowed maximum is: {}", modId, depth, getMaxChunkDepthFor(modId));
|
LOGGER.warn(CHUNK_MANAGER, "The mod {} tried to modify the chunk ticket depth to: {}, its allowed maximum is: {}", modId, depth, getMaxChunkDepthFor(modId));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -453,7 +455,7 @@ public class ForgeChunkManager
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NBTTagCompound forcedChunkData = CompressedStreamTools.read(chunkLoaderData);
|
NBTTagCompound forcedChunkData = CompressedStreamTools.read(chunkLoaderData);
|
||||||
return forcedChunkData.getTagList("TicketList", Constants.NBT.TAG_COMPOUND).tagCount() > 0;
|
return forcedChunkData.getTagList("TicketList", Constants.NBT.TAG_COMPOUND).size() > 0;
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
|
@ -493,11 +495,11 @@ public class ForgeChunkManager
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
FMLLog.log.warn("Unable to read forced chunk data at {} - it will be ignored", chunkLoaderData.getAbsolutePath(), e);
|
LOGGER.warn(CHUNK_MANAGER, "Unable to read forced chunk data at {} - it will be ignored", chunkLoaderData.getAbsolutePath(), e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
NBTTagList ticketList = forcedChunkData.getTagList("TicketList", Constants.NBT.TAG_COMPOUND);
|
NBTTagList ticketList = forcedChunkData.getTagList("TicketList", Constants.NBT.TAG_COMPOUND);
|
||||||
for (int i = 0; i < ticketList.tagCount(); i++)
|
for (int i = 0; i < ticketList.size(); i++)
|
||||||
{
|
{
|
||||||
NBTTagCompound ticketHolder = ticketList.getCompoundTagAt(i);
|
NBTTagCompound ticketHolder = ticketList.getCompoundTagAt(i);
|
||||||
String modId = ticketHolder.getString("Owner");
|
String modId = ticketHolder.getString("Owner");
|
||||||
|
@ -505,18 +507,18 @@ public class ForgeChunkManager
|
||||||
|
|
||||||
if (!isPlayer && !Loader.isModLoaded(modId))
|
if (!isPlayer && !Loader.isModLoaded(modId))
|
||||||
{
|
{
|
||||||
FMLLog.log.warn("Found chunkloading data for mod {} which is currently not available or active - it will be removed from the world save", modId);
|
LOGGER.warn(CHUNK_MANAGER, "Found chunkloading data for mod {} which is currently not available or active - it will be removed from the world save", modId);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isPlayer && !callbacks.containsKey(modId))
|
if (!isPlayer && !callbacks.containsKey(modId))
|
||||||
{
|
{
|
||||||
FMLLog.log.warn("The mod {} has registered persistent chunkloading data but doesn't seem to want to be called back with it - it will be removed from the world save", modId);
|
LOGGER.warn(CHUNK_MANAGER, "The mod {} has registered persistent chunkloading data but doesn't seem to want to be called back with it - it will be removed from the world save", modId);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
NBTTagList tickets = ticketHolder.getTagList("Tickets", Constants.NBT.TAG_COMPOUND);
|
NBTTagList tickets = ticketHolder.getTagList("Tickets", Constants.NBT.TAG_COMPOUND);
|
||||||
for (int j = 0; j < tickets.tagCount(); j++)
|
for (int j = 0; j < tickets.size(); j++)
|
||||||
{
|
{
|
||||||
NBTTagCompound ticket = tickets.getCompoundTagAt(j);
|
NBTTagCompound ticket = tickets.getCompoundTagAt(j);
|
||||||
modId = ticket.hasKey("ModId") ? ticket.getString("ModId") : modId;
|
modId = ticket.hasKey("ModId") ? ticket.getString("ModId") : modId;
|
||||||
|
@ -558,14 +560,14 @@ public class ForgeChunkManager
|
||||||
// force the world to load the entity's chunk
|
// force the world to load the entity's chunk
|
||||||
// the load will come back through the loadEntity method and attach the entity
|
// the load will come back through the loadEntity method and attach the entity
|
||||||
// to the ticket
|
// to the ticket
|
||||||
world.getChunkFromChunkCoords(tick.entityChunkX, tick.entityChunkZ);
|
world.getChunk(tick.entityChunkX, tick.entityChunkZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Ticket tick : ImmutableSet.copyOf(pendingEntities.values()))
|
for (Ticket tick : ImmutableSet.copyOf(pendingEntities.values()))
|
||||||
{
|
{
|
||||||
if (tick.ticketType == Type.ENTITY && tick.entity == null)
|
if (tick.ticketType == Type.ENTITY && tick.entity == null)
|
||||||
{
|
{
|
||||||
FMLLog.log.warn("Failed to load persistent chunkloading entity {} from store.", pendingEntities.inverse().get(tick));
|
LOGGER.warn(CHUNK_MANAGER, "Failed to load persistent chunkloading entity {} from store.", pendingEntities.inverse().get(tick));
|
||||||
loadedTickets.remove(tick.modId, tick);
|
loadedTickets.remove(tick.modId, tick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -587,7 +589,7 @@ public class ForgeChunkManager
|
||||||
}
|
}
|
||||||
if (tickets.size() > maxTicketLength)
|
if (tickets.size() > maxTicketLength)
|
||||||
{
|
{
|
||||||
FMLLog.log.warn("The mod {} has too many open chunkloading tickets {}. Excess will be dropped", modId, tickets.size());
|
LOGGER.warn(CHUNK_MANAGER, "The mod {} has too many open chunkloading tickets {}. Excess will be dropped", modId, tickets.size());
|
||||||
tickets.subList(maxTicketLength, tickets.size()).clear();
|
tickets.subList(maxTicketLength, tickets.size()).clear();
|
||||||
}
|
}
|
||||||
ForgeChunkManager.tickets.get(world).putAll(modId, tickets);
|
ForgeChunkManager.tickets.get(world).putAll(modId, tickets);
|
||||||
|
@ -645,7 +647,7 @@ public class ForgeChunkManager
|
||||||
ModContainer container = getContainer(mod);
|
ModContainer container = getContainer(mod);
|
||||||
if (container == null)
|
if (container == null)
|
||||||
{
|
{
|
||||||
FMLLog.log.warn("Unable to register a callback for an unknown mod {} ({} : {})", mod, mod.getClass().getName(), Integer.toHexString(System.identityHashCode(mod)));
|
LOGGER.warn(CHUNK_MANAGER, "Unable to register a callback for an unknown mod {} ({} : {})", mod, mod.getClass().getName(), Integer.toHexString(System.identityHashCode(mod)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -703,12 +705,12 @@ public class ForgeChunkManager
|
||||||
ModContainer mc = getContainer(mod);
|
ModContainer mc = getContainer(mod);
|
||||||
if (mc == null)
|
if (mc == null)
|
||||||
{
|
{
|
||||||
FMLLog.log.error("Failed to locate the container for mod instance {} ({} : {})", mod, mod.getClass().getName(), Integer.toHexString(System.identityHashCode(mod)));
|
LOGGER.error(CHUNK_MANAGER, "Failed to locate the container for mod instance {} ({} : {})", mod, mod.getClass().getName(), Integer.toHexString(System.identityHashCode(mod)));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (playerTickets.get(player).size()>playerTicketLength)
|
if (playerTickets.get(player).size()>playerTicketLength)
|
||||||
{
|
{
|
||||||
FMLLog.log.warn("Unable to assign further chunkloading tickets to player {} (on behalf of mod {})", player, mc.getModId());
|
LOGGER.warn(CHUNK_MANAGER, "Unable to assign further chunkloading tickets to player {} (on behalf of mod {})", player, mc.getModId());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Ticket ticket = new Ticket(mc.getModId(),type,world,player);
|
Ticket ticket = new Ticket(mc.getModId(),type,world,player);
|
||||||
|
@ -730,13 +732,13 @@ public class ForgeChunkManager
|
||||||
ModContainer container = getContainer(mod);
|
ModContainer container = getContainer(mod);
|
||||||
if (container == null)
|
if (container == null)
|
||||||
{
|
{
|
||||||
FMLLog.log.error("Failed to locate the container for mod instance {} ({} : {})", mod, mod.getClass().getName(), Integer.toHexString(System.identityHashCode(mod)));
|
LOGGER.error(CHUNK_MANAGER, "Failed to locate the container for mod instance {} ({} : {})", mod, mod.getClass().getName(), Integer.toHexString(System.identityHashCode(mod)));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String modId = container.getModId();
|
String modId = container.getModId();
|
||||||
if (!callbacks.containsKey(modId))
|
if (!callbacks.containsKey(modId))
|
||||||
{
|
{
|
||||||
FMLLog.log.fatal("The mod {} has attempted to request a ticket without a listener in place", modId);
|
LOGGER.fatal(CHUNK_MANAGER, "The mod {} has attempted to request a ticket without a listener in place", modId);
|
||||||
throw new RuntimeException("Invalid ticket request");
|
throw new RuntimeException("Invalid ticket request");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -746,7 +748,7 @@ public class ForgeChunkManager
|
||||||
{
|
{
|
||||||
if (!warnedMods.contains(modId))
|
if (!warnedMods.contains(modId))
|
||||||
{
|
{
|
||||||
FMLLog.log.info("The mod {} has attempted to allocate a chunkloading ticket beyond it's currently allocated maximum: {}", modId, allowedCount);
|
LOGGER.info(CHUNK_MANAGER, "The mod {} has attempted to allocate a chunkloading ticket beyond it's currently allocated maximum: {}", modId, allowedCount);
|
||||||
warnedMods.add(modId);
|
warnedMods.add(modId);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -810,7 +812,7 @@ public class ForgeChunkManager
|
||||||
}
|
}
|
||||||
if (ticket.isPlayerTicket() ? !playerTickets.containsValue(ticket) : !tickets.get(ticket.world).containsEntry(ticket.modId, ticket))
|
if (ticket.isPlayerTicket() ? !playerTickets.containsValue(ticket) : !tickets.get(ticket.world).containsEntry(ticket.modId, ticket))
|
||||||
{
|
{
|
||||||
FMLLog.log.fatal("The mod {} attempted to force load a chunk with an invalid ticket. This is not permitted.", ticket.modId);
|
LOGGER.fatal(CHUNK_MANAGER, "The mod {} attempted to force load a chunk with an invalid ticket. This is not permitted.", ticket.modId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ticket.requestedChunks.add(chunk);
|
ticket.requestedChunks.add(chunk);
|
||||||
|
@ -913,7 +915,7 @@ public class ForgeChunkManager
|
||||||
for (String modId : ticketSet.keySet())
|
for (String modId : ticketSet.keySet())
|
||||||
{
|
{
|
||||||
NBTTagCompound ticketHolder = new NBTTagCompound();
|
NBTTagCompound ticketHolder = new NBTTagCompound();
|
||||||
ticketList.appendTag(ticketHolder);
|
ticketList.add(ticketHolder);
|
||||||
|
|
||||||
ticketHolder.setString("Owner", modId);
|
ticketHolder.setString("Owner", modId);
|
||||||
NBTTagList tickets = new NBTTagList();
|
NBTTagList tickets = new NBTTagList();
|
||||||
|
@ -939,11 +941,11 @@ public class ForgeChunkManager
|
||||||
ticket.setInteger("chunkZ", MathHelper.floor(tick.entity.chunkCoordZ));
|
ticket.setInteger("chunkZ", MathHelper.floor(tick.entity.chunkCoordZ));
|
||||||
ticket.setLong("PersistentIDMSB", tick.entity.getPersistentID().getMostSignificantBits());
|
ticket.setLong("PersistentIDMSB", tick.entity.getPersistentID().getMostSignificantBits());
|
||||||
ticket.setLong("PersistentIDLSB", tick.entity.getPersistentID().getLeastSignificantBits());
|
ticket.setLong("PersistentIDLSB", tick.entity.getPersistentID().getLeastSignificantBits());
|
||||||
tickets.appendTag(ticket);
|
tickets.add(ticket);
|
||||||
}
|
}
|
||||||
else if (tick.ticketType != Type.ENTITY)
|
else if (tick.ticketType != Type.ENTITY)
|
||||||
{
|
{
|
||||||
tickets.appendTag(ticket);
|
tickets.add(ticket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -956,7 +958,7 @@ public class ForgeChunkManager
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
FMLLog.log.warn("Unable to write forced chunk data to {} - chunkloading won't work", chunkLoaderData.getAbsolutePath(), e);
|
LOGGER.warn(CHUNK_MANAGER, "Unable to write forced chunk data to {} - chunkloading won't work", chunkLoaderData.getAbsolutePath(), e);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -1025,14 +1027,14 @@ public class ForgeChunkManager
|
||||||
private static void loadChunkEntities(Chunk chunk, NBTTagCompound nbt, World world)
|
private static void loadChunkEntities(Chunk chunk, NBTTagCompound nbt, World world)
|
||||||
{
|
{
|
||||||
NBTTagList entities = nbt.getTagList("Entities", Constants.NBT.TAG_COMPOUND);
|
NBTTagList entities = nbt.getTagList("Entities", Constants.NBT.TAG_COMPOUND);
|
||||||
for (int i = 0; i < entities.tagCount(); ++i)
|
for (int i = 0; i < entities.size(); ++i)
|
||||||
{
|
{
|
||||||
AnvilChunkLoader.readChunkEntity(entities.getCompoundTagAt(i), world, chunk);
|
AnvilChunkLoader.readChunkEntity(entities.getCompoundTagAt(i), world, chunk);
|
||||||
chunk.setHasEntities(true);
|
chunk.setHasEntities(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
NBTTagList tileEntities = nbt.getTagList("TileEntities", Constants.NBT.TAG_COMPOUND);
|
NBTTagList tileEntities = nbt.getTagList("TileEntities", Constants.NBT.TAG_COMPOUND);
|
||||||
for (int i = 0; i < tileEntities.tagCount(); ++i)
|
for (int i = 0; i < tileEntities.size(); ++i)
|
||||||
{
|
{
|
||||||
TileEntity tileEntity = TileEntity.create(world, tileEntities.getCompoundTagAt(i));
|
TileEntity tileEntity = TileEntity.create(world, tileEntities.getCompoundTagAt(i));
|
||||||
if (tileEntity != null) chunk.addTileEntity(tileEntity);
|
if (tileEntity != null) chunk.addTileEntity(tileEntity);
|
||||||
|
@ -1055,7 +1057,7 @@ public class ForgeChunkManager
|
||||||
dest.delete();
|
dest.delete();
|
||||||
}
|
}
|
||||||
cfgFile.renameTo(dest);
|
cfgFile.renameTo(dest);
|
||||||
FMLLog.log.error("A critical error occurred reading the forgeChunkLoading.cfg file, defaults will be used - the invalid file is backed up at forgeChunkLoading.cfg.bak", e);
|
LOGGER.error(CHUNK_MANAGER, "A critical error occurred reading the forgeChunkLoading.cfg file, defaults will be used - the invalid file is backed up at forgeChunkLoading.cfg.bak", e);
|
||||||
}
|
}
|
||||||
syncConfigDefaults();
|
syncConfigDefaults();
|
||||||
}
|
}
|
||||||
|
@ -1108,7 +1110,7 @@ public class ForgeChunkManager
|
||||||
temp.setMinValue(0);
|
temp.setMinValue(0);
|
||||||
dormantChunkCacheSize = temp.getInt(0);
|
dormantChunkCacheSize = temp.getInt(0);
|
||||||
propOrder.add("dormantChunkCacheSize");
|
propOrder.add("dormantChunkCacheSize");
|
||||||
FMLLog.log.info("Configured a dormant chunk cache size of {}", temp.getInt(0));
|
LOGGER.info(CHUNK_MANAGER, "Configured a dormant chunk cache size of {}", temp.getInt(0));
|
||||||
|
|
||||||
temp = config.get("defaults", "asyncChunkLoading", true);
|
temp = config.get("defaults", "asyncChunkLoading", true);
|
||||||
temp.setComment("Load chunks asynchronously for players, reducing load on the server thread.\n" +
|
temp.setComment("Load chunks asynchronously for players, reducing load on the server thread.\n" +
|
||||||
|
|
|
@ -149,13 +149,13 @@ import net.minecraftforge.fml.common.LoaderState;
|
||||||
import net.minecraftforge.fml.ModContainer;
|
import net.minecraftforge.fml.ModContainer;
|
||||||
import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher;
|
import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher;
|
||||||
import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher.ConnectionType;
|
import net.minecraftforge.fml.common.network.handshake.NetworkDispatcher.ConnectionType;
|
||||||
import net.minecraftforge.fml.common.registry.ForgeRegistries;
|
|
||||||
import net.minecraftforge.fml.ModList;
|
import net.minecraftforge.fml.ModList;
|
||||||
import net.minecraftforge.fml.network.ConnectionType;
|
import net.minecraftforge.fml.network.ConnectionType;
|
||||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||||
import net.minecraftforge.fml.network.NetworkHooks;
|
import net.minecraftforge.fml.network.NetworkHooks;
|
||||||
import net.minecraftforge.fml.relauncher.ReflectionHelper;
|
import net.minecraftforge.fml.relauncher.ReflectionHelper;
|
||||||
|
import net.minecraftforge.registries.ForgeRegistries;
|
||||||
import net.minecraftforge.registries.IForgeRegistry;
|
import net.minecraftforge.registries.IForgeRegistry;
|
||||||
import net.minecraftforge.registries.RegistryManager;
|
import net.minecraftforge.registries.RegistryManager;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* Minecraft Forge
|
||||||
|
* Copyright (c) 2016-2018.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation version 2.1
|
||||||
|
* of the License.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package net.minecraftforge.common.asm;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.AnnotationNode;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the final modifier from fields with the @ObjectHolder annotation, prevents the JITer from in lining them so our runtime replacements can work.
|
||||||
|
* Will also de-finalize all fields in on class level annotations.
|
||||||
|
*/
|
||||||
|
public class ObjectHolderDefinalize implements ILaunchPluginService {
|
||||||
|
|
||||||
|
private final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "object_holder_definalize";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void addResource(Path resource, String name) { }
|
||||||
|
|
||||||
|
@Override public <T> T getExtension() { return null; } // ?
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handlesClass(Type classType, boolean isEmpty)
|
||||||
|
{
|
||||||
|
return !isEmpty; //Check for annotations?
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasHolder(List<AnnotationNode> lst)
|
||||||
|
{
|
||||||
|
return lst != null && lst.stream().anyMatch(n -> n.desc.equals(OBJECT_HOLDER));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getValue(List<AnnotationNode> lst)
|
||||||
|
{
|
||||||
|
AnnotationNode ann = lst.stream().filter(n -> n.desc.equals(OBJECT_HOLDER)).findFirst().get();
|
||||||
|
if (ann.values != null)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < ann.values.size() - 1; x += 2) {
|
||||||
|
if (ann.values.get(x).equals("value")) {
|
||||||
|
return (String)ann.values.get(x + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClassNode processClass(ClassNode classNode, Type classType)
|
||||||
|
{
|
||||||
|
//Must be public static finals, and non-array objects
|
||||||
|
final int flags = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
|
||||||
|
|
||||||
|
//Fix Annotated Fields before injecting from class level
|
||||||
|
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
|
||||||
|
{
|
||||||
|
f.access &= ~Opcodes.ACC_FINAL; //Strip final
|
||||||
|
f.access |= Opcodes.ACC_SYNTHETIC; //Add Synthetic so we can check in runtime. ? Good idea?
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasHolder(classNode.visibleAnnotations)) //Class level, de-finalize all fields and add @ObjectHolder to them!
|
||||||
|
{
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
String value = getValue(classNode.visibleAnnotations);
|
||||||
|
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L")).forEach(f ->
|
||||||
|
{
|
||||||
|
f.access &= ~Opcodes.ACC_FINAL;
|
||||||
|
f.access |= Opcodes.ACC_SYNTHETIC;
|
||||||
|
/*if (!hasHolder(f.visibleAnnotations)) //Add field level annotation, doesn't do anything until after we figure out how ASMDataTable is gatherered
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
f.visitAnnotation(OBJECT_HOLDER, true);
|
||||||
|
else
|
||||||
|
f.visitAnnotation(OBJECT_HOLDER, true).visit("value", value + ":" + f.name.toLowerCase());
|
||||||
|
}*/
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return classNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* Minecraft Forge
|
||||||
|
* Copyright (c) 2016-2018.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation version 2.1
|
||||||
|
* of the License.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package net.minecraftforge.common.asm;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.commons.InstructionAdapter;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.objectweb.asm.tree.FieldNode;
|
||||||
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
|
||||||
|
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies specified enums to allow runtime extension by making the $VALUES field non-final and
|
||||||
|
* injecting constructor calls which are not valid in normal java code.
|
||||||
|
*/
|
||||||
|
public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger();
|
||||||
|
private final Type STRING = Type.getType(String.class);
|
||||||
|
private final Type ARRAY_UTILS = Type.getType("Lorg/apache/commons/lang3/ArrayUtils;"); //Don't directly reference this to prevent class loading.
|
||||||
|
private final String ADD_DESC = Type.getMethodDescriptor(Type.getType(Object[].class), Type.getType(Object[].class), Type.getType(Object.class));
|
||||||
|
private final Type ENUM_HELPER = Type.getType("Lnet/minecraftforge/common/util/EnumHelper;"); //Again, not direct reference to prevent class loading.
|
||||||
|
private final String CLEAN_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class));
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "runtime_enum_extender";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void addResource(Path resource, String name) { }
|
||||||
|
|
||||||
|
@Override public <T> T getExtension() { return null; } // ?
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handlesClass(Type classType, boolean isEmpty)
|
||||||
|
{
|
||||||
|
return !isEmpty; //Any way to determine if its a enum at this level?
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClassNode processClass(ClassNode classNode, Type classType)
|
||||||
|
{
|
||||||
|
if ((classNode.access & Opcodes.ACC_ENUM) == 0)
|
||||||
|
return classNode;
|
||||||
|
|
||||||
|
Type array = Type.getType("[" + classType.getInternalName());
|
||||||
|
final int flags = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
|
||||||
|
|
||||||
|
FieldNode values = classNode.fields.stream().filter(f -> f.desc.contentEquals(array.getDescriptor()) && ((f.access & flags) == flags)).findFirst().orElse(null);
|
||||||
|
//Static methods named "create", with first argument as a string, and returning this type
|
||||||
|
classNode.methods.stream().filter(m -> ((m.access & Opcodes.ACC_STATIC) != 0) && m.name.equals("create") && Type.getReturnType(m.desc).equals(classType)).forEach(mtd ->
|
||||||
|
{
|
||||||
|
Type[] args = Type.getArgumentTypes(mtd.desc);
|
||||||
|
if (args.length == 0 || !args[0].equals(STRING))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Type[] ctrArgs = new Type[args.length + 1];
|
||||||
|
ctrArgs[0] = STRING;
|
||||||
|
ctrArgs[1] = Type.INT_TYPE;
|
||||||
|
for (int x = 1; x < args.length; x++)
|
||||||
|
ctrArgs[1 + x] = args[x];
|
||||||
|
|
||||||
|
String desc = Type.getMethodDescriptor(Type.VOID_TYPE, ctrArgs);
|
||||||
|
|
||||||
|
MethodNode ctr = classNode.methods.stream().filter(m -> m.name.equals("<init>") && m.desc.equals(desc)).findFirst().orElse(null);
|
||||||
|
if (ctr == null)
|
||||||
|
{
|
||||||
|
LOGGER.error("Enum has create method with no matching constructor:");
|
||||||
|
LOGGER.error(" Target: " + desc);
|
||||||
|
classNode.methods.stream().filter(m -> m.name.equals("<init>")).forEach(m -> LOGGER.error(" :" + m.desc));
|
||||||
|
throw new IllegalStateException("Enum has create method with no matching constructor: " + desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values == null)
|
||||||
|
{
|
||||||
|
LOGGER.error("Enum has create method but we could not find $VALUES:");
|
||||||
|
classNode.fields.stream().filter(f -> (f.access & Opcodes.ACC_STATIC) != 0).forEach(m -> LOGGER.error(" " + m.name + " " + m.desc));
|
||||||
|
throw new IllegalStateException("Enum has create method but we could not find $VALUES");
|
||||||
|
}
|
||||||
|
|
||||||
|
values.access &= values.access & ~Opcodes.ACC_FINAL; //Strip the final so JITer doesn't inline things.
|
||||||
|
|
||||||
|
mtd.instructions.clear();
|
||||||
|
InstructionAdapter ins = new InstructionAdapter(mtd);
|
||||||
|
//ret = new ThisType(name, VALUES.length + 1, args..)
|
||||||
|
ins.anew(classType);
|
||||||
|
ins.dup();
|
||||||
|
ins.load(0, STRING);
|
||||||
|
ins.getstatic(classType.getInternalName(), values.name, values.desc);
|
||||||
|
ins.iconst(1);
|
||||||
|
ins.add(Type.INT_TYPE);
|
||||||
|
for (int x = 1; x < args.length; x++)
|
||||||
|
ins.load(x, args[x]);
|
||||||
|
ins.invokespecial(classType.getDescriptor(), "<init>", desc, false);
|
||||||
|
ins.store(1, classType);
|
||||||
|
// VALUES = ArrayUtils.add(VALUES, ret)
|
||||||
|
ins.getstatic(classType.getInternalName(), values.name, values.desc);
|
||||||
|
ins.load(1, classType);
|
||||||
|
ins.invokestatic(ARRAY_UTILS.getDescriptor(), "add", ADD_DESC, false);
|
||||||
|
ins.checkcast(array);
|
||||||
|
ins.putstatic(classType.getInternalName(), values.name, values.desc);
|
||||||
|
//EnumHelper.cleanEnumCache(ThisType.class)
|
||||||
|
ins.visitLdcInsn(classType);
|
||||||
|
ins.invokestatic(ENUM_HELPER.getInternalName(), "cleanEnumCache", CLEAN_DESC, false);
|
||||||
|
//return ret
|
||||||
|
ins.load(1, classType);
|
||||||
|
ins.areturn(classType);
|
||||||
|
});
|
||||||
|
return classNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -65,11 +65,11 @@ import net.minecraft.nbt.NBTTagCompound;
|
||||||
import net.minecraft.util.JsonUtils;
|
import net.minecraft.util.JsonUtils;
|
||||||
import net.minecraft.util.NonNullList;
|
import net.minecraft.util.NonNullList;
|
||||||
import net.minecraft.util.ResourceLocation;
|
import net.minecraft.util.ResourceLocation;
|
||||||
import net.minecraftforge.fml.common.registry.ForgeRegistries;
|
|
||||||
import net.minecraftforge.oredict.OreDictionary;
|
import net.minecraftforge.oredict.OreDictionary;
|
||||||
import net.minecraftforge.oredict.OreIngredient;
|
import net.minecraftforge.oredict.OreIngredient;
|
||||||
import net.minecraftforge.oredict.ShapedOreRecipe;
|
import net.minecraftforge.oredict.ShapedOreRecipe;
|
||||||
import net.minecraftforge.oredict.ShapelessOreRecipe;
|
import net.minecraftforge.oredict.ShapelessOreRecipe;
|
||||||
|
import net.minecraftforge.registries.ForgeRegistries;
|
||||||
import net.minecraftforge.registries.ForgeRegistry;
|
import net.minecraftforge.registries.ForgeRegistry;
|
||||||
import net.minecraftforge.registries.GameData;
|
import net.minecraftforge.registries.GameData;
|
||||||
import net.minecraftforge.registries.RegistryManager;
|
import net.minecraftforge.registries.RegistryManager;
|
||||||
|
|
|
@ -29,7 +29,7 @@ import net.minecraft.util.ResourceLocation;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
import net.minecraftforge.fml.common.FMLCommonHandler;
|
import net.minecraftforge.fml.common.FMLCommonHandler;
|
||||||
import net.minecraftforge.fml.common.registry.ForgeRegistries;
|
import net.minecraftforge.registries.ForgeRegistries;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
|
@ -27,207 +27,21 @@ import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import net.minecraftforge.fml.common.EnhancedRuntimeException;
|
import net.minecraftforge.fml.common.EnhancedRuntimeException;
|
||||||
import net.minecraftforge.fml.common.FMLLog;
|
|
||||||
import net.minecraft.block.BlockPressurePlate.Sensitivity;
|
|
||||||
import net.minecraft.block.material.Material;
|
|
||||||
import net.minecraft.enchantment.EnumEnchantmentType;
|
|
||||||
import net.minecraft.entity.EntityLiving;
|
|
||||||
import net.minecraft.entity.EnumCreatureAttribute;
|
|
||||||
import net.minecraft.entity.EnumCreatureType;
|
|
||||||
import net.minecraft.entity.item.EntityPainting.EnumArt;
|
|
||||||
import net.minecraft.entity.passive.HorseArmorType;
|
|
||||||
import net.minecraft.entity.passive.IAnimals;
|
|
||||||
import net.minecraft.entity.player.EntityPlayer.SleepResult;
|
|
||||||
import net.minecraft.item.EnumAction;
|
|
||||||
import net.minecraft.item.EnumRarity;
|
|
||||||
import net.minecraft.item.Item;
|
|
||||||
import net.minecraft.item.Item.ToolMaterial;
|
|
||||||
import net.minecraft.item.ItemArmor.ArmorMaterial;
|
|
||||||
import net.minecraft.util.SoundEvent;
|
|
||||||
import net.minecraft.util.math.BlockPos;
|
|
||||||
import net.minecraft.util.math.RayTraceResult;
|
|
||||||
import net.minecraft.util.text.TextFormatting;
|
|
||||||
import net.minecraft.world.EnumSkyBlock;
|
|
||||||
import net.minecraft.world.IBlockAccess;
|
|
||||||
import net.minecraft.world.gen.structure.StructureStrongholdPieces.Stronghold.Door;
|
|
||||||
import net.minecraftforge.classloading.FMLForgePlugin;
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public class EnumHelper
|
public class EnumHelper
|
||||||
{
|
{
|
||||||
private static Object reflectionFactory = null;
|
/*
|
||||||
private static Method newConstructorAccessor = null;
|
|
||||||
private static Method newInstance = null;
|
|
||||||
private static Method newFieldAccessor = null;
|
|
||||||
private static Method fieldAccessorSet = null;
|
|
||||||
private static boolean isSetup = false;
|
|
||||||
|
|
||||||
//Some enums are decompiled with extra arguments, so lets check for that
|
|
||||||
private static Class<?>[][] commonTypes =
|
|
||||||
{
|
|
||||||
{EnumAction.class},
|
|
||||||
{ArmorMaterial.class, String.class, int.class, int[].class, int.class, SoundEvent.class, float.class},
|
|
||||||
{EnumArt.class, String.class, int.class, int.class, int.class, int.class},
|
|
||||||
{EnumCreatureAttribute.class},
|
|
||||||
{EnumCreatureType.class, Class.class, int.class, Material.class, boolean.class, boolean.class},
|
|
||||||
{Door.class},
|
|
||||||
{EnumEnchantmentType.class, Predicate.class},
|
|
||||||
{Sensitivity.class},
|
|
||||||
{RayTraceResult.Type.class},
|
|
||||||
{EnumSkyBlock.class, int.class},
|
|
||||||
{SleepResult.class},
|
|
||||||
{ToolMaterial.class, int.class, int.class, float.class, float.class, int.class},
|
|
||||||
{EnumRarity.class, TextFormatting.class, String.class},
|
|
||||||
{HorseArmorType.class, String.class, int.class},
|
|
||||||
{EntityLiving.SpawnPlacementType.class, BiPredicate.class}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static EnumAction addAction(String name)
|
|
||||||
{
|
|
||||||
return addEnum(EnumAction.class, name);
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public static ArmorMaterial addArmorMaterial(String name, String textureName, int durability, int[] reductionAmounts, int enchantability, SoundEvent soundOnEquip, float toughness)
|
|
||||||
{
|
|
||||||
return addEnum(ArmorMaterial.class, name, textureName, durability, reductionAmounts, enchantability, soundOnEquip, toughness);
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public static EnumArt addArt(String name, String tile, int sizeX, int sizeY, int offsetX, int offsetY)
|
|
||||||
{
|
|
||||||
return addEnum(EnumArt.class, name, tile, sizeX, sizeY, offsetX, offsetY);
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public static EnumCreatureAttribute addCreatureAttribute(String name)
|
|
||||||
{
|
|
||||||
return addEnum(EnumCreatureAttribute.class, name);
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public static EnumCreatureType addCreatureType(String name, Class<? extends IAnimals> typeClass, int maxNumber, Material material, boolean peaceful, boolean animal)
|
|
||||||
{
|
|
||||||
return addEnum(EnumCreatureType.class, name, typeClass, maxNumber, material, peaceful, animal);
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public static Door addDoor(String name)
|
|
||||||
{
|
|
||||||
return addEnum(Door.class, name);
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public static EnumEnchantmentType addEnchantmentType(String name, Predicate<Item> delegate)
|
|
||||||
{
|
|
||||||
return addEnum(EnumEnchantmentType.class, name, delegate);
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public static Sensitivity addSensitivity(String name)
|
|
||||||
{
|
|
||||||
return addEnum(Sensitivity.class, name);
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public static RayTraceResult.Type addMovingObjectType(String name)
|
|
||||||
{
|
|
||||||
return addEnum(RayTraceResult.Type.class, name);
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public static EnumSkyBlock addSkyBlock(String name, int lightValue)
|
|
||||||
{
|
|
||||||
return addEnum(EnumSkyBlock.class, name, lightValue);
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public static SleepResult addStatus(String name)
|
|
||||||
{
|
|
||||||
return addEnum(SleepResult.class, name);
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public static ToolMaterial addToolMaterial(String name, int harvestLevel, int maxUses, float efficiency, float damage, int enchantability)
|
|
||||||
{
|
|
||||||
return addEnum(ToolMaterial.class, name, harvestLevel, maxUses, efficiency, damage, enchantability);
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public static EnumRarity addRarity(String name, TextFormatting color, String displayName)
|
|
||||||
{
|
|
||||||
return addEnum(EnumRarity.class, name, color, displayName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static EntityLiving.SpawnPlacementType addSpawnPlacementType(String name, BiPredicate<IBlockAccess, BlockPos> predicate)
|
|
||||||
{
|
|
||||||
return addEnum(EntityLiving.SpawnPlacementType.class, name, predicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param name the name of the new {@code HorseArmorType}
|
|
||||||
* @param textureLocation the path to the texture for this armor type. It must follow the format domain:path and be relative to the assets folder.
|
|
||||||
* @param armorStrength how much protection this armor type should give
|
|
||||||
* @return the new {@code HorseArmorType}, or null if it could not be created
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static HorseArmorType addHorseArmor(String name, String textureLocation, int armorStrength)
|
|
||||||
{
|
|
||||||
return addEnum(HorseArmorType.class, name, textureLocation, armorStrength);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setup()
|
|
||||||
{
|
|
||||||
if (isSetup)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Method getReflectionFactory = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("getReflectionFactory");
|
|
||||||
reflectionFactory = getReflectionFactory.invoke(null);
|
|
||||||
newConstructorAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newConstructorAccessor", Constructor.class);
|
|
||||||
newInstance = Class.forName("sun.reflect.ConstructorAccessor").getDeclaredMethod("newInstance", Object[].class);
|
|
||||||
newFieldAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newFieldAccessor", Field.class, boolean.class);
|
|
||||||
fieldAccessorSet = Class.forName("sun.reflect.FieldAccessor").getDeclaredMethod("set", Object.class, Object.class);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
FMLLog.log.error("Error setting up EnumHelper.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
isSetup = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Everything below this is found at the site below, and updated to be able to compile in Eclipse/Java 1.6+
|
* Everything below this is found at the site below, and updated to be able to compile in Eclipse/Java 1.6+
|
||||||
* Also modified for use in decompiled code.
|
* Also modified for use in decompiled code.
|
||||||
* Found at: http://niceideas.ch/roller2/badtrash/entry/java_create_enum_instances_dynamically
|
* Found at: http://niceideas.ch/roller2/badtrash/entry/java_create_enum_instances_dynamically
|
||||||
*/
|
*/
|
||||||
private static Object getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes) throws Exception
|
|
||||||
{
|
|
||||||
Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
|
|
||||||
parameterTypes[0] = String.class;
|
|
||||||
parameterTypes[1] = int.class;
|
|
||||||
System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);
|
|
||||||
return newConstructorAccessor.invoke(reflectionFactory, enumClass.getDeclaredConstructor(parameterTypes));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static < T extends Enum<? >> T makeEnum(Class<T> enumClass, @Nullable String value, int ordinal, Class<?>[] additionalTypes, @Nullable Object[] additionalValues) throws Exception
|
private static < T extends Enum<? >> T makeEnum(Class<T> enumClass, @Nullable String value, int ordinal, Class<?>[] additionalTypes, @Nullable Object[] additionalValues) throws Exception
|
||||||
{
|
{
|
||||||
int additionalParamsCount = additionalValues == null ? 0 : additionalValues.length;
|
//new Enum(name, values.size() + 1, params)
|
||||||
Object[] params = new Object[additionalParamsCount + 2];
|
|
||||||
params[0] = value;
|
|
||||||
params[1] = ordinal;
|
|
||||||
if (additionalValues != null)
|
|
||||||
{
|
|
||||||
System.arraycopy(additionalValues, 0, params, 2, additionalValues.length);
|
|
||||||
}
|
|
||||||
return enumClass.cast(newInstance.invoke(getConstructorAccessor(enumClass, additionalTypes), new Object[] {params}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setFailsafeFieldValue(Field field, @Nullable Object target, @Nullable Object value) throws Exception
|
|
||||||
{
|
|
||||||
field.setAccessible(true);
|
|
||||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
|
||||||
modifiersField.setAccessible(true);
|
|
||||||
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
|
|
||||||
Object fieldAccessor = newFieldAccessor.invoke(reflectionFactory, field, false);
|
|
||||||
fieldAccessorSet.invoke(fieldAccessor, target, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void blankField(Class<?> enumClass, String fieldName) throws Exception
|
private static void blankField(Class<?> enumClass, String fieldName) throws Exception
|
||||||
|
@ -237,190 +51,35 @@ public class EnumHelper
|
||||||
if (field.getName().contains(fieldName))
|
if (field.getName().contains(fieldName))
|
||||||
{
|
{
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
setFailsafeFieldValue(field, enumClass, null);
|
field.set(enumClass, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void cleanEnumCache(Class<?> enumClass) throws Exception
|
//Class holds caches of the enum constants used for Enum.valueOf() and reflection magic. So we need to invalidate those caches when we add something new. They will automtically be re-built.
|
||||||
|
public static void cleanEnumCache(Class<?> enumClass) throws Exception
|
||||||
{
|
{
|
||||||
blankField(enumClass, "enumConstantDirectory");
|
blankField(enumClass, "enumConstantDirectory");
|
||||||
blankField(enumClass, "enumConstants");
|
blankField(enumClass, "enumConstants");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static <T extends Enum<? >> T addEnum(Class<T> enumType, String enumName, Object... paramValues)
|
|
||||||
{
|
|
||||||
setup();
|
|
||||||
return addEnum(commonTypes, enumType, enumName, paramValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected static <T extends Enum<? >> T addEnum(Class<?>[][] map, Class<T> enumType, String enumName, Object... paramValues)
|
|
||||||
{
|
|
||||||
for (Class<?>[] lookup : map)
|
|
||||||
{
|
|
||||||
if (lookup[0] == enumType)
|
|
||||||
{
|
|
||||||
Class<?>[] paramTypes = new Class<?>[lookup.length - 1];
|
|
||||||
if (paramTypes.length > 0)
|
|
||||||
{
|
|
||||||
System.arraycopy(lookup, 1, paramTypes, 0, paramTypes.length);
|
|
||||||
}
|
|
||||||
return addEnum(enumType, enumName, paramTypes, paramValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Tests an enum is compatible with these args, throws an error if not.
|
|
||||||
public static void testEnum(Class<? extends Enum<?>> enumType, Class<?>[] paramTypes)
|
|
||||||
{
|
|
||||||
addEnum(true, enumType, null, paramTypes, (Object[])null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static <T extends Enum<? >> T addEnum(Class<T> enumType, String enumName, Class<?>[] paramTypes, Object... paramValues)
|
|
||||||
{
|
|
||||||
return addEnum(false, enumType, enumName, paramTypes, paramValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked", "serial" })
|
@SuppressWarnings({ "unchecked", "serial" })
|
||||||
@Nullable
|
@Nullable
|
||||||
private static <T extends Enum<? >> T addEnum(boolean test, final Class<T> enumType, @Nullable String enumName, final Class<?>[] paramTypes, @Nullable Object[] paramValues)
|
private static <T extends Enum<?> & IExpandableEnum<?>> T addEnum(boolean test, final Class<T> enumType, T newValue)
|
||||||
{
|
{
|
||||||
if (!isSetup)
|
|
||||||
{
|
|
||||||
setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
Field valuesField = null;
|
|
||||||
Field[] fields = enumType.getDeclaredFields();
|
|
||||||
|
|
||||||
for (Field field : fields)
|
|
||||||
{
|
|
||||||
String name = field.getName();
|
|
||||||
if (name.equals("$VALUES") || name.equals("ENUM$VALUES")) //Added 'ENUM$VALUES' because Eclipse's internal compiler doesn't follow standards
|
|
||||||
{
|
|
||||||
valuesField = field;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int flags = (FMLForgePlugin.RUNTIME_DEOBF ? Modifier.PUBLIC : Modifier.PRIVATE) | Modifier.STATIC | Modifier.FINAL | 0x1000 /*SYNTHETIC*/;
|
|
||||||
if (valuesField == null)
|
|
||||||
{
|
|
||||||
String valueType = String.format("[L%s;", enumType.getName().replace('.', '/'));
|
|
||||||
|
|
||||||
for (Field field : fields)
|
|
||||||
{
|
|
||||||
if ((field.getModifiers() & flags) == flags &&
|
|
||||||
field.getType().getName().replace('.', '/').equals(valueType)) //Apparently some JVMs return .'s and some don't..
|
|
||||||
{
|
|
||||||
valuesField = field;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valuesField == null)
|
|
||||||
{
|
|
||||||
final List<String> lines = Lists.newArrayList();
|
|
||||||
lines.add(String.format("Could not find $VALUES field for enum: %s", enumType.getName()));
|
|
||||||
lines.add(String.format("Runtime Deobf: %s", FMLForgePlugin.RUNTIME_DEOBF));
|
|
||||||
lines.add(String.format("Flags: %s", String.format("%16s", Integer.toBinaryString(flags)).replace(' ', '0')));
|
|
||||||
lines.add( "Fields:");
|
|
||||||
for (Field field : fields)
|
|
||||||
{
|
|
||||||
String mods = String.format("%16s", Integer.toBinaryString(field.getModifiers())).replace(' ', '0');
|
|
||||||
lines.add(String.format(" %s %s: %s", mods, field.getName(), field.getType().getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String line : lines)
|
|
||||||
FMLLog.log.fatal(line);
|
|
||||||
|
|
||||||
if (test)
|
|
||||||
{
|
|
||||||
throw new EnhancedRuntimeException("Could not find $VALUES field for enum: " + enumType.getName())
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected void printStackTrace(WrappedPrintStream stream)
|
|
||||||
{
|
|
||||||
for (String line : lines)
|
|
||||||
stream.println(line);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (test)
|
|
||||||
{
|
|
||||||
Object ctr = null;
|
|
||||||
Exception ex = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ctr = getConstructorAccessor(enumType, paramTypes);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
ex = e;
|
|
||||||
}
|
|
||||||
if (ctr == null || ex != null)
|
|
||||||
{
|
|
||||||
throw new EnhancedRuntimeException(String.format("Could not find constructor for Enum %s", enumType.getName()), ex)
|
|
||||||
{
|
|
||||||
private String toString(Class<?>[] cls)
|
|
||||||
{
|
|
||||||
StringBuilder b = new StringBuilder();
|
|
||||||
for (int x = 0; x < cls.length; x++)
|
|
||||||
{
|
|
||||||
b.append(cls[x].getName());
|
|
||||||
if (x != cls.length - 1)
|
|
||||||
b.append(", ");
|
|
||||||
}
|
|
||||||
return b.toString();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected void printStackTrace(WrappedPrintStream stream)
|
|
||||||
{
|
|
||||||
stream.println("Target Arguments:");
|
|
||||||
stream.println(" java.lang.String, int, " + toString(paramTypes));
|
|
||||||
stream.println("Found Constructors:");
|
|
||||||
for (Constructor<?> ctr : enumType.getDeclaredConstructors())
|
|
||||||
{
|
|
||||||
stream.println(" " + toString(ctr.getParameterTypes()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
valuesField.setAccessible(true);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
T[] previousValues = (T[])valuesField.get(enumType);
|
T[] previousValues = enumType.getEnumConstants();
|
||||||
T newValue = makeEnum(enumType, enumName, previousValues.length, paramTypes, paramValues);
|
T newValue = makeEnum(enumType, enumName, previousValues.length, paramTypes, paramValues);
|
||||||
setFailsafeFieldValue(valuesField, null, ArrayUtils.add(previousValues, newValue));
|
newValue.__set_values__(ArrayUtils.add(previousValues, newValue));
|
||||||
cleanEnumCache(enumType);
|
cleanEnumCache(enumType);
|
||||||
|
|
||||||
return newValue;
|
return newValue;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
FMLLog.log.error("Error adding enum with EnumHelper.", e);
|
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
|
||||||
{
|
|
||||||
if (!isSetup)
|
|
||||||
{
|
|
||||||
setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
/*
|
|
||||||
* ByteArraySeekableSource.java
|
|
||||||
*
|
|
||||||
* Created on May 17, 2006, 12:41 PM
|
|
||||||
* Copyright (c) 2006 Heiko Klein
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.fml.repackage.com.nothome.delta;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a byte buffer as a source
|
|
||||||
*/
|
|
||||||
public class ByteBufferSeekableSource implements SeekableSource {
|
|
||||||
|
|
||||||
private ByteBuffer bb;
|
|
||||||
private ByteBuffer cur;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new ByteArraySeekableSource.
|
|
||||||
*/
|
|
||||||
public ByteBufferSeekableSource(byte[] source) {
|
|
||||||
this(ByteBuffer.wrap(source));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new ByteArraySeekableSource.
|
|
||||||
*/
|
|
||||||
public ByteBufferSeekableSource(ByteBuffer bb) {
|
|
||||||
if (bb == null)
|
|
||||||
throw new NullPointerException("bb");
|
|
||||||
this.bb = bb;
|
|
||||||
bb.rewind();
|
|
||||||
try {
|
|
||||||
seek(0);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void seek(long pos) throws IOException {
|
|
||||||
cur = bb.slice();
|
|
||||||
if (pos > cur.limit())
|
|
||||||
throw new IOException("pos " + pos + " cannot seek " + cur.limit());
|
|
||||||
cur.position((int) pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(ByteBuffer dest) throws IOException {
|
|
||||||
if (!cur.hasRemaining())
|
|
||||||
return -1;
|
|
||||||
int c = 0;
|
|
||||||
while (cur.hasRemaining() && dest.hasRemaining()) {
|
|
||||||
dest.put(cur.get());
|
|
||||||
c++;
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
bb = null;
|
|
||||||
cur = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a debug <code>String</code>.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "BBSeekable" +
|
|
||||||
" bb=" + this.bb.position() + "-" + bb.limit() +
|
|
||||||
" cur=" + this.cur.position() + "-" + cur.limit() +
|
|
||||||
"";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001 Torgeir Veimo
|
|
||||||
* Copyright (c) 2002 Nicolas PERIDONT
|
|
||||||
* Copyright (c) 2006 Heiko Klein
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.fml.repackage.com.nothome.delta;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checksum computation class.
|
|
||||||
*/
|
|
||||||
public class Checksum {
|
|
||||||
|
|
||||||
static final boolean debug = false;
|
|
||||||
|
|
||||||
private Map<Long,Integer> checksums = Maps.newHashMap();
|
|
||||||
|
|
||||||
private static final char single_hash[] = {
|
|
||||||
/* Random numbers generated using SLIB's pseudo-random number generator. */
|
|
||||||
0xbcd1, 0xbb65, 0x42c2, 0xdffe, 0x9666, 0x431b, 0x8504, 0xeb46,
|
|
||||||
0x6379, 0xd460, 0xcf14, 0x53cf, 0xdb51, 0xdb08, 0x12c8, 0xf602,
|
|
||||||
0xe766, 0x2394, 0x250d, 0xdcbb, 0xa678, 0x02af, 0xa5c6, 0x7ea6,
|
|
||||||
0xb645, 0xcb4d, 0xc44b, 0xe5dc, 0x9fe6, 0x5b5c, 0x35f5, 0x701a,
|
|
||||||
0x220f, 0x6c38, 0x1a56, 0x4ca3, 0xffc6, 0xb152, 0x8d61, 0x7a58,
|
|
||||||
0x9025, 0x8b3d, 0xbf0f, 0x95a3, 0xe5f4, 0xc127, 0x3bed, 0x320b,
|
|
||||||
0xb7f3, 0x6054, 0x333c, 0xd383, 0x8154, 0x5242, 0x4e0d, 0x0a94,
|
|
||||||
0x7028, 0x8689, 0x3a22, 0x0980, 0x1847, 0xb0f1, 0x9b5c, 0x4176,
|
|
||||||
0xb858, 0xd542, 0x1f6c, 0x2497, 0x6a5a, 0x9fa9, 0x8c5a, 0x7743,
|
|
||||||
0xa8a9, 0x9a02, 0x4918, 0x438c, 0xc388, 0x9e2b, 0x4cad, 0x01b6,
|
|
||||||
0xab19, 0xf777, 0x365f, 0x1eb2, 0x091e, 0x7bf8, 0x7a8e, 0x5227,
|
|
||||||
0xeab1, 0x2074, 0x4523, 0xe781, 0x01a3, 0x163d, 0x3b2e, 0x287d,
|
|
||||||
0x5e7f, 0xa063, 0xb134, 0x8fae, 0x5e8e, 0xb7b7, 0x4548, 0x1f5a,
|
|
||||||
0xfa56, 0x7a24, 0x900f, 0x42dc, 0xcc69, 0x02a0, 0x0b22, 0xdb31,
|
|
||||||
0x71fe, 0x0c7d, 0x1732, 0x1159, 0xcb09, 0xe1d2, 0x1351, 0x52e9,
|
|
||||||
0xf536, 0x5a4f, 0xc316, 0x6bf9, 0x8994, 0xb774, 0x5f3e, 0xf6d6,
|
|
||||||
0x3a61, 0xf82c, 0xcc22, 0x9d06, 0x299c, 0x09e5, 0x1eec, 0x514f,
|
|
||||||
0x8d53, 0xa650, 0x5c6e, 0xc577, 0x7958, 0x71ac, 0x8916, 0x9b4f,
|
|
||||||
0x2c09, 0x5211, 0xf6d8, 0xcaaa, 0xf7ef, 0x287f, 0x7a94, 0xab49,
|
|
||||||
0xfa2c, 0x7222, 0xe457, 0xd71a, 0x00c3, 0x1a76, 0xe98c, 0xc037,
|
|
||||||
0x8208, 0x5c2d, 0xdfda, 0xe5f5, 0x0b45, 0x15ce, 0x8a7e, 0xfcad,
|
|
||||||
0xaa2d, 0x4b5c, 0xd42e, 0xb251, 0x907e, 0x9a47, 0xc9a6, 0xd93f,
|
|
||||||
0x085e, 0x35ce, 0xa153, 0x7e7b, 0x9f0b, 0x25aa, 0x5d9f, 0xc04d,
|
|
||||||
0x8a0e, 0x2875, 0x4a1c, 0x295f, 0x1393, 0xf760, 0x9178, 0x0f5b,
|
|
||||||
0xfa7d, 0x83b4, 0x2082, 0x721d, 0x6462, 0x0368, 0x67e2, 0x8624,
|
|
||||||
0x194d, 0x22f6, 0x78fb, 0x6791, 0xb238, 0xb332, 0x7276, 0xf272,
|
|
||||||
0x47ec, 0x4504, 0xa961, 0x9fc8, 0x3fdc, 0xb413, 0x007a, 0x0806,
|
|
||||||
0x7458, 0x95c6, 0xccaa, 0x18d6, 0xe2ae, 0x1b06, 0xf3f6, 0x5050,
|
|
||||||
0xc8e8, 0xf4ac, 0xc04c, 0xf41c, 0x992f, 0xae44, 0x5f1b, 0x1113,
|
|
||||||
0x1738, 0xd9a8, 0x19ea, 0x2d33, 0x9698, 0x2fe9, 0x323f, 0xcde2,
|
|
||||||
0x6d71, 0xe37d, 0xb697, 0x2c4f, 0x4373, 0x9102, 0x075d, 0x8e25,
|
|
||||||
0x1672, 0xec28, 0x6acb, 0x86cc, 0x186e, 0x9414, 0xd674, 0xd1a5
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize checksums for source. The checksum for the <code>chunkSize</code> bytes at offset
|
|
||||||
* <code>chunkSize</code> * i is inserted into a hash map.
|
|
||||||
*/
|
|
||||||
public Checksum(SeekableSource source, int chunkSize) throws IOException {
|
|
||||||
ByteBuffer bb = ByteBuffer.allocate(chunkSize * 2);
|
|
||||||
int count = 0;
|
|
||||||
while (true) {
|
|
||||||
source.read(bb);
|
|
||||||
bb.flip();
|
|
||||||
if (bb.remaining() < chunkSize)
|
|
||||||
break;
|
|
||||||
while (bb.remaining() >= chunkSize) {
|
|
||||||
long queryChecksum = queryChecksum0(bb, chunkSize);
|
|
||||||
checksums.put(queryChecksum, count++);
|
|
||||||
}
|
|
||||||
bb.compact();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the checksum computed from the buffer.
|
|
||||||
* Marks, gets, then resets the buffer.
|
|
||||||
*/
|
|
||||||
public static long queryChecksum(ByteBuffer bb, int len) {
|
|
||||||
bb.mark();
|
|
||||||
long sum = queryChecksum0(bb, len);
|
|
||||||
bb.reset();
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long queryChecksum0(ByteBuffer bb, int len) {
|
|
||||||
int high = 0; int low = 0;
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
low += single_hash[bb.get()+128];
|
|
||||||
high += low;
|
|
||||||
}
|
|
||||||
return ((high & 0xffff) << 16) | (low & 0xffff);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increments a checksum.
|
|
||||||
* @param checksum initial checksum
|
|
||||||
* @param out byte leaving view
|
|
||||||
* @param in byte entering view
|
|
||||||
* @param chunkSize size of chunks
|
|
||||||
* @return new checksum
|
|
||||||
*/
|
|
||||||
public static long incrementChecksum(long checksum, byte out, byte in, int chunkSize) {
|
|
||||||
char old_c = single_hash[out+128];
|
|
||||||
char new_c = single_hash[in+128];
|
|
||||||
int low = ((int)((checksum) & 0xffff) - old_c + new_c) & 0xffff;
|
|
||||||
int high = ((int)((checksum) >> 16) - (old_c * chunkSize) + low) & 0xffff;
|
|
||||||
return (high << 16) | (low & 0xffff);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 256 random hash values.
|
|
||||||
*/
|
|
||||||
public static char[] getSingleHash() {
|
|
||||||
return single_hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the index of a checksum.
|
|
||||||
*/
|
|
||||||
public int findChecksumIndex(long hashf) {
|
|
||||||
if (!checksums.containsKey(hashf))
|
|
||||||
return -1;
|
|
||||||
return checksums.get(hashf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a debug <code>String</code>.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return super.toString() +
|
|
||||||
" checksums=" + this.checksums;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001 Torgeir Veimo
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.fml.repackage.com.nothome.delta;
|
|
||||||
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For debugging patch generation.
|
|
||||||
*/
|
|
||||||
public class DebugDiffWriter implements DiffWriter {
|
|
||||||
|
|
||||||
private ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new DebugDiffWriter.
|
|
||||||
*/
|
|
||||||
public DebugDiffWriter() {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addCopy(long offset, int length) throws IOException {
|
|
||||||
if (os.size() > 0)
|
|
||||||
writeBuf();
|
|
||||||
System.err.println("COPY off: " + offset + ", len: " + length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addData(byte b) throws IOException {
|
|
||||||
os.write(b);
|
|
||||||
writeBuf();
|
|
||||||
}
|
|
||||||
private void writeBuf() {
|
|
||||||
System.err.print("DATA: ");
|
|
||||||
byte[] ba = os.toByteArray();
|
|
||||||
for (int ix = 0; ix < ba.length; ix++) {
|
|
||||||
if (ba[ix] == '\n')
|
|
||||||
System.err.print("\\n");
|
|
||||||
else
|
|
||||||
System.err.print(String.valueOf((char) ba[ix]));
|
|
||||||
//System.err.print("0x" + Integer.toHexString(buf[ix]) + " "); // hex output
|
|
||||||
}
|
|
||||||
System.err.println("");
|
|
||||||
os.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void flush() throws IOException {
|
|
||||||
System.err.println("FLUSH");
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
System.err.println("CLOSE");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,447 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001 Torgeir Veimo
|
|
||||||
* Copyright (c) 2002 Nicolas PERIDONT
|
|
||||||
* Bug Fixes: Daniel Morrione dan@morrione.net
|
|
||||||
* Copyright (c) 2006 Heiko Klein
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* Change Log:
|
|
||||||
* iiimmddyyn nnnnn Description
|
|
||||||
* ---------- ----- -------------------------------------------------------
|
|
||||||
* gls100603a Fixes from Torgeir Veimo and Dan Morrione
|
|
||||||
* gls110603a Stream not being closed thus preventing a file from
|
|
||||||
* being subsequently deleted.
|
|
||||||
* gls031504a Error being written to stderr rather than throwing exception
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.fml.repackage.com.nothome.delta;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.channels.ReadableByteChannel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class for computing deltas against a source.
|
|
||||||
* The source file is read by blocks and a hash is computed per block.
|
|
||||||
* Then the target is scanned for matching blocks.
|
|
||||||
* <p/>
|
|
||||||
* This class is not thread safe. Use one instance per thread.
|
|
||||||
* <p/>
|
|
||||||
* This class should support files over 4GB in length, although you must
|
|
||||||
* use a larger checksum size, such as 1K, as all checksums use "int" indexing.
|
|
||||||
* Newer versions may eventually support paging in/out of checksums.
|
|
||||||
*/
|
|
||||||
public class Delta {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Debug flag.
|
|
||||||
*/
|
|
||||||
final static boolean debug = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default size of 16.
|
|
||||||
* For "Lorem ipsum" text files (see the tests) the ideal size is about 14.
|
|
||||||
* Any smaller and the patch size becomes actually be larger.
|
|
||||||
* <p>
|
|
||||||
* Use a size like 64 or 128 for large files.
|
|
||||||
*/
|
|
||||||
public static final int DEFAULT_CHUNK_SIZE = 1<<4;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chunk Size.
|
|
||||||
*/
|
|
||||||
private int S;
|
|
||||||
|
|
||||||
private SourceState source;
|
|
||||||
private TargetState target;
|
|
||||||
private DiffWriter output;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new Delta.
|
|
||||||
* In the future, additional constructor arguments will set the algorithm details.
|
|
||||||
*/
|
|
||||||
public Delta() {
|
|
||||||
setChunkSize(DEFAULT_CHUNK_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the chunk size used.
|
|
||||||
* Larger chunks are faster and use less memory, but create larger patches
|
|
||||||
* as well.
|
|
||||||
*
|
|
||||||
* @param size
|
|
||||||
*/
|
|
||||||
public void setChunkSize(int size) {
|
|
||||||
if (size <= 0)
|
|
||||||
throw new IllegalArgumentException("Invalid size");
|
|
||||||
S = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares the source bytes with target bytes, writing to output.
|
|
||||||
*/
|
|
||||||
public void compute(byte source[], byte target[], OutputStream output)
|
|
||||||
throws IOException {
|
|
||||||
compute(new ByteBufferSeekableSource(source),
|
|
||||||
new ByteArrayInputStream(target),
|
|
||||||
new GDiffWriter(output));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares the source bytes with target bytes, returning output.
|
|
||||||
*/
|
|
||||||
public byte[] compute(byte source[], byte target[])
|
|
||||||
throws IOException {
|
|
||||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
||||||
compute(source, target, os);
|
|
||||||
return os.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares the source bytes with target input, writing to output.
|
|
||||||
*/
|
|
||||||
public void compute(byte[] sourceBytes, InputStream inputStream,
|
|
||||||
DiffWriter diffWriter) throws IOException
|
|
||||||
{
|
|
||||||
compute(new ByteBufferSeekableSource(sourceBytes),
|
|
||||||
inputStream, diffWriter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares the source file with a target file, writing to output.
|
|
||||||
*
|
|
||||||
* @param output will be closed
|
|
||||||
*/
|
|
||||||
public void compute(File sourceFile, File targetFile, DiffWriter output)
|
|
||||||
throws IOException {
|
|
||||||
RandomAccessFileSeekableSource source = new RandomAccessFileSeekableSource(new RandomAccessFile(sourceFile, "r"));
|
|
||||||
InputStream is = new BufferedInputStream(new FileInputStream(targetFile));
|
|
||||||
try {
|
|
||||||
compute(source, is, output);
|
|
||||||
} finally {
|
|
||||||
source.close();
|
|
||||||
is.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares the source with a target, writing to output.
|
|
||||||
*
|
|
||||||
* @param output will be closed
|
|
||||||
*/
|
|
||||||
public void compute(SeekableSource seekSource, InputStream targetIS, DiffWriter output)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
debug("using match length S = " + S);
|
|
||||||
}
|
|
||||||
|
|
||||||
source = new SourceState(seekSource);
|
|
||||||
target = new TargetState(targetIS);
|
|
||||||
this.output = output;
|
|
||||||
if (debug)
|
|
||||||
debug("checksums " + source.checksum);
|
|
||||||
|
|
||||||
while (!target.eof()) {
|
|
||||||
debug("!target.eof()");
|
|
||||||
int index = target.find(source);
|
|
||||||
if (index != -1) {
|
|
||||||
if (debug)
|
|
||||||
debug("found hash " + index);
|
|
||||||
long offset = (long)index * S;
|
|
||||||
source.seek(offset);
|
|
||||||
int match = target.longestMatch(source);
|
|
||||||
if (match >= S) {
|
|
||||||
if (debug)
|
|
||||||
debug("output.addCopy("+offset+","+match+")");
|
|
||||||
output.addCopy(offset, match);
|
|
||||||
} else {
|
|
||||||
// move the position back according to how much we can't copy
|
|
||||||
target.tbuf.position(target.tbuf.position() - match);
|
|
||||||
addData();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
addData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addData() throws IOException {
|
|
||||||
int i = target.read();
|
|
||||||
if (debug)
|
|
||||||
debug("addData " + Integer.toHexString(i));
|
|
||||||
if (i == -1)
|
|
||||||
return;
|
|
||||||
output.addData((byte)i);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SourceState {
|
|
||||||
|
|
||||||
private Checksum checksum;
|
|
||||||
private SeekableSource source;
|
|
||||||
|
|
||||||
public SourceState(SeekableSource source) throws IOException {
|
|
||||||
checksum = new Checksum(source, S);
|
|
||||||
this.source = source;
|
|
||||||
source.seek(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void seek(long index) throws IOException {
|
|
||||||
source.seek(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a debug <code>String</code>.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "Source"+
|
|
||||||
" checksum=" + this.checksum +
|
|
||||||
" source=" + this.source +
|
|
||||||
"";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class TargetState {
|
|
||||||
|
|
||||||
private ReadableByteChannel c;
|
|
||||||
private ByteBuffer tbuf = ByteBuffer.allocate(blocksize());
|
|
||||||
private ByteBuffer sbuf = ByteBuffer.allocate(blocksize());
|
|
||||||
private long hash;
|
|
||||||
private boolean hashReset = true;
|
|
||||||
private boolean eof;
|
|
||||||
|
|
||||||
TargetState(InputStream targetIS) throws IOException {
|
|
||||||
c = Channels.newChannel(targetIS);
|
|
||||||
tbuf.limit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int blocksize() {
|
|
||||||
return Math.min(1024 * 16, S * 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of the next N bytes of the stream.
|
|
||||||
*/
|
|
||||||
public int find(SourceState source) throws IOException {
|
|
||||||
if (eof)
|
|
||||||
return -1;
|
|
||||||
sbuf.clear();
|
|
||||||
sbuf.limit(0);
|
|
||||||
if (hashReset) {
|
|
||||||
debug("hashReset");
|
|
||||||
while (tbuf.remaining() < S) {
|
|
||||||
tbuf.compact();
|
|
||||||
int read = c.read(tbuf);
|
|
||||||
tbuf.flip();
|
|
||||||
if (read == -1) {
|
|
||||||
debug("target ending");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hash = Checksum.queryChecksum(tbuf, S);
|
|
||||||
hashReset = false;
|
|
||||||
}
|
|
||||||
if (debug)
|
|
||||||
debug("hash " + hash + " " + dump());
|
|
||||||
return source.checksum.findChecksumIndex(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean eof() {
|
|
||||||
return eof;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a byte.
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public int read() throws IOException {
|
|
||||||
if (tbuf.remaining() <= S) {
|
|
||||||
readMore();
|
|
||||||
if (!tbuf.hasRemaining()) {
|
|
||||||
eof = true;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
byte b = tbuf.get();
|
|
||||||
if (tbuf.remaining() >= S) {
|
|
||||||
byte nchar = tbuf.get( tbuf.position() + S -1 );
|
|
||||||
hash = Checksum.incrementChecksum(hash, b, nchar, S);
|
|
||||||
} else {
|
|
||||||
debug("out of char");
|
|
||||||
}
|
|
||||||
return b & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the longest match length at the source location.
|
|
||||||
*/
|
|
||||||
public int longestMatch(SourceState source) throws IOException {
|
|
||||||
debug("longestMatch");
|
|
||||||
int match = 0;
|
|
||||||
hashReset = true;
|
|
||||||
while (true) {
|
|
||||||
if (!sbuf.hasRemaining()) {
|
|
||||||
sbuf.clear();
|
|
||||||
int read = source.source.read(sbuf);
|
|
||||||
sbuf.flip();
|
|
||||||
if (read == -1)
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
if (!tbuf.hasRemaining()) {
|
|
||||||
readMore();
|
|
||||||
if (!tbuf.hasRemaining()) {
|
|
||||||
debug("target ending");
|
|
||||||
eof = true;
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sbuf.get() != tbuf.get()) {
|
|
||||||
tbuf.position(tbuf.position() - 1);
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
match++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readMore() throws IOException {
|
|
||||||
if (debug)
|
|
||||||
debug("readMore " + tbuf);
|
|
||||||
tbuf.compact();
|
|
||||||
c.read(tbuf);
|
|
||||||
tbuf.flip();
|
|
||||||
}
|
|
||||||
|
|
||||||
void hash() {
|
|
||||||
hash = Checksum.queryChecksum(tbuf, S);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a debug <code>String</code>.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "Target[" +
|
|
||||||
" targetBuff=" + dump() + // this.tbuf +
|
|
||||||
" sourceBuff=" + this.sbuf +
|
|
||||||
" hashf=" + this.hash +
|
|
||||||
" eof=" + this.eof +
|
|
||||||
"]";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String dump() { return dump(tbuf); }
|
|
||||||
|
|
||||||
private String dump(ByteBuffer bb) {
|
|
||||||
return getTextDump(bb);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void append(StringBuffer sb, int value) {
|
|
||||||
char b1 = (char)((value >> 4) & 0x0F);
|
|
||||||
char b2 = (char)((value) & 0x0F);
|
|
||||||
sb.append( Character.forDigit(b1, 16) );
|
|
||||||
sb.append( Character.forDigit(b2, 16) );
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTextDump(ByteBuffer bb)
|
|
||||||
{
|
|
||||||
StringBuffer sb = new StringBuffer(bb.remaining() * 2);
|
|
||||||
bb.mark();
|
|
||||||
while (bb.hasRemaining()) {
|
|
||||||
int val = bb.get();
|
|
||||||
if (val > 32 && val < 127)
|
|
||||||
sb.append(" ").append((char)val);
|
|
||||||
else
|
|
||||||
append(sb, val);
|
|
||||||
}
|
|
||||||
bb.reset();
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a patch using file names.
|
|
||||||
*/
|
|
||||||
public static void main(String argv[]) throws Exception {
|
|
||||||
if (argv.length != 3) {
|
|
||||||
System.err.println("usage Delta [-d] source target [output]");
|
|
||||||
System.err.println("either -d or an output filename must be specified.");
|
|
||||||
System.err.println("aborting..");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DiffWriter output = null;
|
|
||||||
File sourceFile = null;
|
|
||||||
File targetFile = null;
|
|
||||||
if (argv[0].equals("-d")) {
|
|
||||||
sourceFile = new File(argv[1]);
|
|
||||||
targetFile = new File(argv[2]);
|
|
||||||
output = new DebugDiffWriter();
|
|
||||||
} else {
|
|
||||||
sourceFile = new File(argv[0]);
|
|
||||||
targetFile = new File(argv[1]);
|
|
||||||
output =
|
|
||||||
new GDiffWriter(
|
|
||||||
new DataOutputStream(
|
|
||||||
new BufferedOutputStream(
|
|
||||||
new FileOutputStream(new File(argv[2])))));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceFile.length() > Integer.MAX_VALUE
|
|
||||||
|| targetFile.length() > Integer.MAX_VALUE) {
|
|
||||||
System.err.println(
|
|
||||||
"source or target is too large, max length is "
|
|
||||||
+ Integer.MAX_VALUE);
|
|
||||||
System.err.println("aborting..");
|
|
||||||
output.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Delta d = new Delta();
|
|
||||||
d.compute(sourceFile, targetFile, output);
|
|
||||||
|
|
||||||
output.flush();
|
|
||||||
output.close();
|
|
||||||
if (debug) //gls031504a
|
|
||||||
System.out.println("finished generating delta");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void debug(String s) {
|
|
||||||
if (debug)
|
|
||||||
System.err.println(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001 Torgeir Veimo
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.fml.repackage.com.nothome.delta;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for DIFF writers.
|
|
||||||
*/
|
|
||||||
public interface DiffWriter extends Closeable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a GDIFF copy instruction.
|
|
||||||
*/
|
|
||||||
public void addCopy(long offset, int length) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a GDIFF data instruction.
|
|
||||||
* Implementors should buffer the data.
|
|
||||||
*/
|
|
||||||
public void addData(byte b) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes to output, e.g. any data added.
|
|
||||||
*/
|
|
||||||
public void flush() throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes this stream.
|
|
||||||
* Note that {@link DiffWriter} will invoke this method at the end.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,248 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001 Torgeir Veimo
|
|
||||||
* Copyright (c) 2006 Heiko Klein
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.fml.repackage.com.nothome.delta;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
|
|
||||||
import static net.minecraftforge.fml.repackage.com.nothome.delta.GDiffWriter.COPY_INT_INT;
|
|
||||||
import static net.minecraftforge.fml.repackage.com.nothome.delta.GDiffWriter.COPY_INT_UBYTE;
|
|
||||||
import static net.minecraftforge.fml.repackage.com.nothome.delta.GDiffWriter.COPY_INT_USHORT;
|
|
||||||
import static net.minecraftforge.fml.repackage.com.nothome.delta.GDiffWriter.COPY_LONG_INT;
|
|
||||||
import static net.minecraftforge.fml.repackage.com.nothome.delta.GDiffWriter.COPY_USHORT_INT;
|
|
||||||
import static net.minecraftforge.fml.repackage.com.nothome.delta.GDiffWriter.COPY_USHORT_UBYTE;
|
|
||||||
import static net.minecraftforge.fml.repackage.com.nothome.delta.GDiffWriter.COPY_USHORT_USHORT;
|
|
||||||
import static net.minecraftforge.fml.repackage.com.nothome.delta.GDiffWriter.DATA_INT;
|
|
||||||
import static net.minecraftforge.fml.repackage.com.nothome.delta.GDiffWriter.DATA_MAX;
|
|
||||||
import static net.minecraftforge.fml.repackage.com.nothome.delta.GDiffWriter.DATA_USHORT;
|
|
||||||
import static net.minecraftforge.fml.repackage.com.nothome.delta.GDiffWriter.EOF;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class patches an input file with a GDIFF patch file.
|
|
||||||
*
|
|
||||||
* The patch file follows the GDIFF file specification available at
|
|
||||||
*
|
|
||||||
* <a href="http://www.w3.org/TR/NOTE-gdiff-19970901.html">http://www.w3.org/TR/NOTE-gdiff-19970901.html</a>.
|
|
||||||
*/
|
|
||||||
public class GDiffPatcher {
|
|
||||||
|
|
||||||
private ByteBuffer buf = ByteBuffer.allocate(1024);
|
|
||||||
private byte buf2[] = buf.array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new GDiffPatcher.
|
|
||||||
*/
|
|
||||||
public GDiffPatcher() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Patches to an output file.
|
|
||||||
*/
|
|
||||||
public void patch(File sourceFile, File patchFile, File outputFile)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
RandomAccessFileSeekableSource source =new RandomAccessFileSeekableSource(new RandomAccessFile(sourceFile, "r"));
|
|
||||||
InputStream patch = null;
|
|
||||||
OutputStream output = null;
|
|
||||||
try {
|
|
||||||
patch = new FileInputStream(patchFile);
|
|
||||||
output = new FileOutputStream(outputFile);
|
|
||||||
patch(source, patch, output);
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(source);
|
|
||||||
IOUtils.closeQuietly(patch);
|
|
||||||
IOUtils.closeQuietly(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Patches to an output stream.
|
|
||||||
*/
|
|
||||||
public void patch(byte[] source, InputStream patch, OutputStream output) throws IOException {
|
|
||||||
patch(new ByteBufferSeekableSource(source), patch, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Patches in memory, returning the patch result.
|
|
||||||
*/
|
|
||||||
public byte[] patch(byte[] source, byte[] patch) throws IOException {
|
|
||||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
||||||
patch(source, new ByteArrayInputStream(patch), os);
|
|
||||||
return os.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Patches to an output stream.
|
|
||||||
*/
|
|
||||||
public void patch(SeekableSource source, InputStream patch, OutputStream out) throws IOException {
|
|
||||||
|
|
||||||
DataOutputStream outOS = new DataOutputStream(out);
|
|
||||||
DataInputStream patchIS = new DataInputStream(patch);
|
|
||||||
|
|
||||||
// the magic string is 'd1 ff d1 ff' + the version number
|
|
||||||
if (patchIS.readUnsignedByte() != 0xd1 ||
|
|
||||||
patchIS.readUnsignedByte() != 0xff ||
|
|
||||||
patchIS.readUnsignedByte() != 0xd1 ||
|
|
||||||
patchIS.readUnsignedByte() != 0xff ||
|
|
||||||
patchIS.readUnsignedByte() != 0x04) {
|
|
||||||
|
|
||||||
throw new PatchException("magic string not found, aborting!");
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
int command = patchIS.readUnsignedByte();
|
|
||||||
if (command == EOF)
|
|
||||||
break;
|
|
||||||
int length;
|
|
||||||
int offset;
|
|
||||||
|
|
||||||
if (command <= DATA_MAX) {
|
|
||||||
append(command, patchIS, outOS);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (command) {
|
|
||||||
case DATA_USHORT: // ushort, n bytes following; append
|
|
||||||
length = patchIS.readUnsignedShort();
|
|
||||||
append(length, patchIS, outOS);
|
|
||||||
break;
|
|
||||||
case DATA_INT: // int, n bytes following; append
|
|
||||||
length = patchIS.readInt();
|
|
||||||
append(length, patchIS, outOS);
|
|
||||||
break;
|
|
||||||
case COPY_USHORT_UBYTE:
|
|
||||||
offset = patchIS.readUnsignedShort();
|
|
||||||
length = patchIS.readUnsignedByte();
|
|
||||||
copy(offset, length, source, outOS);
|
|
||||||
break;
|
|
||||||
case COPY_USHORT_USHORT:
|
|
||||||
offset = patchIS.readUnsignedShort();
|
|
||||||
length = patchIS.readUnsignedShort();
|
|
||||||
copy(offset, length, source, outOS);
|
|
||||||
break;
|
|
||||||
case COPY_USHORT_INT:
|
|
||||||
offset = patchIS.readUnsignedShort();
|
|
||||||
length = patchIS.readInt();
|
|
||||||
copy(offset, length, source, outOS);
|
|
||||||
break;
|
|
||||||
case COPY_INT_UBYTE:
|
|
||||||
offset = patchIS.readInt();
|
|
||||||
length = patchIS.readUnsignedByte();
|
|
||||||
copy(offset, length, source, outOS);
|
|
||||||
break;
|
|
||||||
case COPY_INT_USHORT:
|
|
||||||
offset = patchIS.readInt();
|
|
||||||
length = patchIS.readUnsignedShort();
|
|
||||||
copy(offset, length, source, outOS);
|
|
||||||
break;
|
|
||||||
case COPY_INT_INT:
|
|
||||||
offset = patchIS.readInt();
|
|
||||||
length = patchIS.readInt();
|
|
||||||
copy(offset, length, source, outOS);
|
|
||||||
break;
|
|
||||||
case COPY_LONG_INT:
|
|
||||||
long loffset = patchIS.readLong();
|
|
||||||
length = patchIS.readInt();
|
|
||||||
copy(loffset, length, source, outOS);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("command " + command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outOS.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copy(long offset, int length, SeekableSource source, OutputStream output)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
source.seek(offset);
|
|
||||||
while (length > 0) {
|
|
||||||
int len = Math.min(buf.capacity(), length);
|
|
||||||
buf.clear().limit(len);
|
|
||||||
int res = source.read(buf);
|
|
||||||
if (res == -1)
|
|
||||||
throw new EOFException("in copy " + offset + " " + length);
|
|
||||||
output.write(buf.array(), 0, res);
|
|
||||||
length -= res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void append(int length, InputStream patch, OutputStream output) throws IOException {
|
|
||||||
while (length > 0) {
|
|
||||||
int len = Math.min(buf2.length, length);
|
|
||||||
int res = patch.read(buf2, 0, len);
|
|
||||||
if (res == -1)
|
|
||||||
throw new EOFException("cannot read " + length);
|
|
||||||
output.write(buf2, 0, res);
|
|
||||||
length -= res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple command line tool to patch a file.
|
|
||||||
*/
|
|
||||||
public static void main(String argv[]) {
|
|
||||||
|
|
||||||
if (argv.length != 3) {
|
|
||||||
System.err.println("usage GDiffPatch source patch output");
|
|
||||||
System.err.println("aborting..");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
File sourceFile = new File(argv[0]);
|
|
||||||
File patchFile = new File(argv[1]);
|
|
||||||
File outputFile = new File(argv[2]);
|
|
||||||
|
|
||||||
if (sourceFile.length() > Integer.MAX_VALUE ||
|
|
||||||
patchFile.length() > Integer.MAX_VALUE) {
|
|
||||||
System.err.println("source or patch is too large, max length is " + Integer.MAX_VALUE);
|
|
||||||
System.err.println("aborting..");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
GDiffPatcher patcher = new GDiffPatcher();
|
|
||||||
patcher.patch(sourceFile, patchFile, outputFile);
|
|
||||||
|
|
||||||
System.out.println("finished patching file");
|
|
||||||
|
|
||||||
} catch (Exception ioe) { //gls031504a
|
|
||||||
System.err.println("error while patching: " + ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,178 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001 Torgeir Veimo
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.fml.repackage.com.nothome.delta;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Outputs a diff following the GDIFF file specification available at
|
|
||||||
* http://www.w3.org/TR/NOTE-gdiff-19970901.html.
|
|
||||||
*/
|
|
||||||
public class GDiffWriter implements DiffWriter {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Max length of a chunk.
|
|
||||||
*/
|
|
||||||
public static final int CHUNK_SIZE = Short.MAX_VALUE;
|
|
||||||
|
|
||||||
public static final byte EOF = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Max length for single length data encode.
|
|
||||||
*/
|
|
||||||
public static final int DATA_MAX = 246;
|
|
||||||
|
|
||||||
public static final int DATA_USHORT = 247;
|
|
||||||
public static final int DATA_INT = 248;
|
|
||||||
public static final int COPY_USHORT_UBYTE = 249;
|
|
||||||
public static final int COPY_USHORT_USHORT = 250;
|
|
||||||
public static final int COPY_USHORT_INT = 251;
|
|
||||||
public static final int COPY_INT_UBYTE = 252;
|
|
||||||
public static final int COPY_INT_USHORT = 253;
|
|
||||||
public static final int COPY_INT_INT = 254;
|
|
||||||
public static final int COPY_LONG_INT = 255;
|
|
||||||
|
|
||||||
private ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
private boolean debug = false;
|
|
||||||
|
|
||||||
private DataOutputStream output = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new GDiffWriter.
|
|
||||||
*/
|
|
||||||
public GDiffWriter(DataOutputStream os) throws IOException {
|
|
||||||
this.output = os;
|
|
||||||
// write magic string "d1 ff d1 ff 04"
|
|
||||||
output.writeByte(0xd1);
|
|
||||||
output.writeByte(0xff);
|
|
||||||
output.writeByte(0xd1);
|
|
||||||
output.writeByte(0xff);
|
|
||||||
output.writeByte(0x04);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new GDiffWriter.
|
|
||||||
*/
|
|
||||||
public GDiffWriter(OutputStream output) throws IOException {
|
|
||||||
this(new DataOutputStream(output));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addCopy(long offset, int length) throws IOException {
|
|
||||||
writeBuf();
|
|
||||||
|
|
||||||
//output debug data
|
|
||||||
if (debug)
|
|
||||||
System.err.println("COPY off: " + offset + ", len: " + length);
|
|
||||||
|
|
||||||
// output real data
|
|
||||||
if (offset > Integer.MAX_VALUE) {
|
|
||||||
// Actually, we don't support longer files than int.MAX_VALUE at the moment..
|
|
||||||
output.writeByte(COPY_LONG_INT);
|
|
||||||
output.writeLong(offset);
|
|
||||||
output.writeInt(length);
|
|
||||||
} else if (offset < 65536) {
|
|
||||||
if (length < 256) {
|
|
||||||
output.writeByte(COPY_USHORT_UBYTE);
|
|
||||||
output.writeShort((int)offset);
|
|
||||||
output.writeByte(length);
|
|
||||||
} else if (length > 65535) {
|
|
||||||
output.writeByte(COPY_USHORT_INT);
|
|
||||||
output.writeShort((int)offset);
|
|
||||||
output.writeInt(length);
|
|
||||||
} else {
|
|
||||||
output.writeByte(COPY_USHORT_USHORT);
|
|
||||||
output.writeShort((int)offset);
|
|
||||||
output.writeShort(length);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (length < 256) {
|
|
||||||
output.writeByte(COPY_INT_UBYTE);
|
|
||||||
output.writeInt((int)offset);
|
|
||||||
output.writeByte(length);
|
|
||||||
} else if (length > 65535) {
|
|
||||||
output.writeByte(COPY_INT_INT);
|
|
||||||
output.writeInt((int)offset);
|
|
||||||
output.writeInt(length);
|
|
||||||
} else {
|
|
||||||
output.writeByte(COPY_INT_USHORT);
|
|
||||||
output.writeInt((int)offset);
|
|
||||||
output.writeShort(length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a data byte.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void addData(byte b) throws IOException {
|
|
||||||
buf.write(b);
|
|
||||||
if (buf.size() >= CHUNK_SIZE)
|
|
||||||
writeBuf();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeBuf() throws IOException {
|
|
||||||
if (buf.size() > 0) {
|
|
||||||
if (buf.size() <= DATA_MAX) {
|
|
||||||
output.writeByte(buf.size());
|
|
||||||
} else if (buf.size() <= 65535) {
|
|
||||||
output.writeByte(DATA_USHORT);
|
|
||||||
output.writeShort(buf.size());
|
|
||||||
} else {
|
|
||||||
output.writeByte(DATA_INT);
|
|
||||||
output.writeInt(buf.size());
|
|
||||||
}
|
|
||||||
buf.writeTo(output);
|
|
||||||
buf.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes accumulated data bytes, if any.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void flush() throws IOException
|
|
||||||
{
|
|
||||||
writeBuf();
|
|
||||||
output.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the final EOF byte, closes the underlying stream.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
this.flush();
|
|
||||||
output.write(EOF);
|
|
||||||
output.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* PatchException.java
|
|
||||||
*
|
|
||||||
* Created on June 6, 2006, 9:34 PM
|
|
||||||
* Copyright (c) 2006 Heiko Klein
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.fml.repackage.com.nothome.delta;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when a patch is invalid.
|
|
||||||
*/
|
|
||||||
public class PatchException extends IOException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance of <code>PatchException</code> without detail message.
|
|
||||||
*/
|
|
||||||
public PatchException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance of <code>PatchException</code> with the specified detail message.
|
|
||||||
* @param msg the detail message.
|
|
||||||
*/
|
|
||||||
public PatchException(String msg) {
|
|
||||||
super(msg);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
* RandomAccessFileSeekableSource.java
|
|
||||||
*
|
|
||||||
* Created on May 17, 2006, 1:45 PM
|
|
||||||
* Copyright (c) 2006 Heiko Klein
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.fml.repackage.com.nothome.delta;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a random access file.
|
|
||||||
*/
|
|
||||||
public class RandomAccessFileSeekableSource implements SeekableSource {
|
|
||||||
|
|
||||||
private RandomAccessFile raf;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new RandomAccessFileSeekableSource.
|
|
||||||
* @param raf
|
|
||||||
*/
|
|
||||||
public RandomAccessFileSeekableSource(RandomAccessFile raf) {
|
|
||||||
if (raf == null)
|
|
||||||
throw new NullPointerException("raf");
|
|
||||||
this.raf = raf;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void seek(long pos) throws IOException {
|
|
||||||
raf.seek(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
|
||||||
return raf.read(b, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long length() throws IOException {
|
|
||||||
return raf.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
raf.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(ByteBuffer bb) throws IOException {
|
|
||||||
int c = raf.read(bb.array(), bb.position(), bb.remaining());
|
|
||||||
if (c == -1)
|
|
||||||
return -1;
|
|
||||||
bb.position(bb.position() + c);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* SeekableSource.java
|
|
||||||
*
|
|
||||||
* Created on May 17, 2006, 12:33 PM
|
|
||||||
* Copyright (c) 2006 Heiko Klein
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.minecraftforge.fml.repackage.com.nothome.delta;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For sources of random-access data, such as {@link RandomAccessFile}.
|
|
||||||
*/
|
|
||||||
public interface SeekableSource extends Closeable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the position for the next {@link #read(ByteBuffer)}.
|
|
||||||
*/
|
|
||||||
void seek(long pos) throws IOException ;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads up to {@link ByteBuffer#remaining()} bytes from the source,
|
|
||||||
* returning the number of bytes read, or -1 if no bytes were read
|
|
||||||
* and EOF was reached.
|
|
||||||
*/
|
|
||||||
int read(ByteBuffer bb) throws IOException;
|
|
||||||
|
|
||||||
}
|
|
|
@ -31,7 +31,8 @@ import net.minecraft.util.ResourceLocation;
|
||||||
import net.minecraft.util.registry.IRegistry;
|
import net.minecraft.util.registry.IRegistry;
|
||||||
import net.minecraftforge.client.model.ModelLoader;
|
import net.minecraftforge.client.model.ModelLoader;
|
||||||
import net.minecraftforge.fml.common.FMLLog;
|
import net.minecraftforge.fml.common.FMLLog;
|
||||||
import net.minecraftforge.fml.common.registry.ForgeRegistries;
|
import net.minecraftforge.registries.ForgeRegistries;
|
||||||
|
|
||||||
import org.apache.logging.log4j.message.SimpleMessage;
|
import org.apache.logging.log4j.message.SimpleMessage;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package net.minecraftforge.fml.common.registry;
|
package net.minecraftforge.registries;
|
||||||
|
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.block.Block;
|
||||||
import net.minecraft.enchantment.Enchantment;
|
import net.minecraft.enchantment.Enchantment;
|
||||||
|
@ -29,10 +29,9 @@ import net.minecraft.potion.Potion;
|
||||||
import net.minecraft.potion.PotionType;
|
import net.minecraft.potion.PotionType;
|
||||||
import net.minecraft.util.SoundEvent;
|
import net.minecraft.util.SoundEvent;
|
||||||
import net.minecraft.world.biome.Biome;
|
import net.minecraft.world.biome.Biome;
|
||||||
|
import net.minecraftforge.fml.common.registry.GameRegistry;
|
||||||
|
import net.minecraftforge.fml.common.registry.VillagerRegistry;
|
||||||
import net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession;
|
import net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession;
|
||||||
import net.minecraftforge.registries.GameData;
|
|
||||||
import net.minecraftforge.registries.IForgeRegistry;
|
|
||||||
import net.minecraftforge.registries.RegistryManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that exposes static references to all vanilla and Forge registries.
|
* A class that exposes static references to all vanilla and Forge registries.
|
|
@ -198,19 +198,9 @@ public class ForgeRegistry<V extends IForgeRegistryEntry<V>> implements IForgeRe
|
||||||
return Collections.unmodifiableSet(this.names.keySet());
|
return Collections.unmodifiableSet(this.names.keySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use {@link #getValuesCollection} to avoid copying
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
|
||||||
public List<V> getValues()
|
|
||||||
{
|
|
||||||
return ImmutableList.copyOf(this.names.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public Collection<V> getValuesCollection()
|
public Collection<V> getValues()
|
||||||
{
|
{
|
||||||
return Collections.unmodifiableSet(this.names.values());
|
return Collections.unmodifiableSet(this.names.values());
|
||||||
}
|
}
|
||||||
|
@ -793,7 +783,7 @@ public class ForgeRegistry<V extends IForgeRegistryEntry<V>> implements IForgeRe
|
||||||
NBTTagCompound tag = new NBTTagCompound();
|
NBTTagCompound tag = new NBTTagCompound();
|
||||||
tag.setString("K", e.getKey().toString());
|
tag.setString("K", e.getKey().toString());
|
||||||
tag.setInteger("V", e.getValue());
|
tag.setInteger("V", e.getValue());
|
||||||
ids.appendTag(tag);
|
ids.add(tag);
|
||||||
});
|
});
|
||||||
data.setTag("ids", ids);
|
data.setTag("ids", ids);
|
||||||
|
|
||||||
|
@ -803,7 +793,7 @@ public class ForgeRegistry<V extends IForgeRegistryEntry<V>> implements IForgeRe
|
||||||
NBTTagCompound tag = new NBTTagCompound();
|
NBTTagCompound tag = new NBTTagCompound();
|
||||||
tag.setString("K", e.getKey().toString());
|
tag.setString("K", e.getKey().toString());
|
||||||
tag.setString("V", e.getKey().toString());
|
tag.setString("V", e.getKey().toString());
|
||||||
aliases.appendTag(tag);
|
aliases.add(tag);
|
||||||
});
|
});
|
||||||
data.setTag("aliases", aliases);
|
data.setTag("aliases", aliases);
|
||||||
|
|
||||||
|
@ -813,7 +803,7 @@ public class ForgeRegistry<V extends IForgeRegistryEntry<V>> implements IForgeRe
|
||||||
NBTTagCompound tag = new NBTTagCompound();
|
NBTTagCompound tag = new NBTTagCompound();
|
||||||
tag.setString("K", e.getKey().toString());
|
tag.setString("K", e.getKey().toString());
|
||||||
tag.setString("V", e.getValue());
|
tag.setString("V", e.getValue());
|
||||||
overrides.appendTag(tag);
|
overrides.add(tag);
|
||||||
});
|
});
|
||||||
data.setTag("overrides", overrides);
|
data.setTag("overrides", overrides);
|
||||||
|
|
||||||
|
@ -821,7 +811,7 @@ public class ForgeRegistry<V extends IForgeRegistryEntry<V>> implements IForgeRe
|
||||||
data.setIntArray("blocked", blocked);
|
data.setIntArray("blocked", blocked);
|
||||||
|
|
||||||
NBTTagList dummied = new NBTTagList();
|
NBTTagList dummied = new NBTTagList();
|
||||||
this.dummied.stream().sorted().forEach(e -> dummied.appendTag(new NBTTagString(e.toString())));
|
this.dummied.stream().sorted().forEach(e -> dummied.add(new NBTTagString(e.toString())));
|
||||||
data.setTag("dummied", dummied);
|
data.setTag("dummied", dummied);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -846,24 +836,7 @@ public class ForgeRegistry<V extends IForgeRegistryEntry<V>> implements IForgeRe
|
||||||
list.forEach(e ->
|
list.forEach(e ->
|
||||||
{
|
{
|
||||||
NBTTagCompound comp = (NBTTagCompound)e;
|
NBTTagCompound comp = (NBTTagCompound)e;
|
||||||
String v = comp.getString("V");
|
ret.overrides.put(new ResourceLocation(comp.getString("K")), comp.getString("V"));
|
||||||
if (v.indexOf(':') == -1) //Forge Bug: https://github.com/MinecraftForge/MinecraftForge/issues/4894 TODO: Remove in 1.13
|
|
||||||
{
|
|
||||||
ret.overrides.put(new ResourceLocation(comp.getString("K")), v);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ResourceLocation aliask = new ResourceLocation(comp.getString("K"));
|
|
||||||
ResourceLocation aliasv = new ResourceLocation(v);
|
|
||||||
if (aliasv.equals(aliask))
|
|
||||||
{
|
|
||||||
FMLLog.log.warn("Found unrecoverable 4894 bugged alias/override: {} -> {}, skipping.", aliask, aliasv);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ret.aliases.put(aliask, aliasv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
list = nbt.getTagList("overrides", 10);
|
list = nbt.getTagList("overrides", 10);
|
||||||
|
|
|
@ -30,7 +30,7 @@ import javax.annotation.Nullable;
|
||||||
* if another entry overrides existing ones in the registry.
|
* if another entry overrides existing ones in the registry.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public abstract class ForgeRegistryEntry<V extends ForgeRegistryEntry<V>> implements IForgeRegistryEntry<V>
|
public abstract class ForgeRegistryEntry<V extends IForgeRegistryEntry<V>> implements IForgeRegistryEntry<V>
|
||||||
{
|
{
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
private final TypeToken<V> token = new TypeToken<V>(getClass()){};
|
private final TypeToken<V> token = new TypeToken<V>(getClass()){};
|
||||||
|
|
|
@ -21,7 +21,7 @@ package net.minecraftforge.registries;
|
||||||
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
public interface IForgeRegistryModifiable<V extends ForgeRegistryEntry<V>> extends IForgeRegistry<V>
|
public interface IForgeRegistryModifiable<V extends IForgeRegistryEntry<V>> extends IForgeRegistry<V>
|
||||||
{
|
{
|
||||||
void clear();
|
void clear();
|
||||||
V remove(ResourceLocation key);
|
V remove(ResourceLocation key);
|
||||||
|
|
|
@ -122,7 +122,7 @@ class NamespacedDefaultedWrapper<V extends IForgeRegistryEntry<V>> extends Regis
|
||||||
@Nullable
|
@Nullable
|
||||||
public V getRandomObject(Random random)
|
public V getRandomObject(Random random)
|
||||||
{
|
{
|
||||||
Collection<V> values = this.delegate.getValuesCollection();
|
Collection<V> values = this.delegate.getValues();
|
||||||
return values.stream().skip(random.nextInt(values.size())).findFirst().orElse(null);
|
return values.stream().skip(random.nextInt(values.size())).findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -117,7 +117,7 @@ class NamespacedWrapper<V extends IForgeRegistryEntry<V>> extends RegistryNamesp
|
||||||
@Nullable
|
@Nullable
|
||||||
public V getRandomObject(Random random)
|
public V getRandomObject(Random random)
|
||||||
{
|
{
|
||||||
Collection<V> values = this.delegate.getValuesCollection();
|
Collection<V> values = this.delegate.getValues();
|
||||||
return values.stream().skip(random.nextInt(values.size())).findFirst().orElse(null);
|
return values.stream().skip(random.nextInt(values.size())).findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Minecraft Forge
|
||||||
|
* Copyright (c) 2016-2018.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation version 2.1
|
||||||
|
* of the License.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package net.minecraftforge.registries;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ObjectHolder can be used to automatically populate public static final fields with entries
|
||||||
|
* from the registry. These values can then be referred within mod code directly.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.TYPE, ElementType.FIELD})
|
||||||
|
public @interface ObjectHolder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* If used on a class, this represents a modid only.
|
||||||
|
* If used on a field, it represents a name, which can be abbreviated or complete.
|
||||||
|
* Abbreviated names derive their modid from an enclosing ObjectHolder at the class level.
|
||||||
|
*
|
||||||
|
* @return either a modid or a name based on the rules above
|
||||||
|
*/
|
||||||
|
String value();
|
||||||
|
}
|
|
@ -20,7 +20,6 @@
|
||||||
package net.minecraftforge.registries;
|
package net.minecraftforge.registries;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -28,23 +27,23 @@ import java.util.Queue;
|
||||||
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
|
||||||
import net.minecraftforge.fml.common.FMLLog;
|
|
||||||
import net.minecraftforge.fml.common.registry.GameRegistry.ObjectHolder;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal class used in tracking {@link ObjectHolder} references
|
* Internal class used in tracking {@link ObjectHolder} references
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
class ObjectHolderRef
|
class ObjectHolderRef
|
||||||
{
|
{
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger();
|
||||||
private Field field;
|
private Field field;
|
||||||
private ResourceLocation injectedObject;
|
private ResourceLocation injectedObject;
|
||||||
private boolean isValid;
|
private boolean isValid;
|
||||||
private ForgeRegistry<?> registry;
|
private ForgeRegistry<?> registry;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
ObjectHolderRef(Field field, ResourceLocation injectedObject, boolean extractFromExistingValues)
|
ObjectHolderRef(Field field, ResourceLocation injectedObject, boolean extractFromExistingValues)
|
||||||
{
|
{
|
||||||
registry = getRegistryForType(field);
|
registry = getRegistryForType(field);
|
||||||
|
@ -66,7 +65,7 @@ class ObjectHolderRef
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.injectedObject = ((ForgeRegistryEntry)existing).getRegistryName();
|
this.injectedObject = ((IForgeRegistryEntry)existing).getRegistryName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IllegalAccessException e)
|
catch (IllegalAccessException e)
|
||||||
|
@ -83,13 +82,12 @@ class ObjectHolderRef
|
||||||
{
|
{
|
||||||
throw new IllegalStateException(String.format("The ObjectHolder annotation cannot apply to a field that does not map to a registry. Ensure the registry was created during the RegistryEvent.NewRegistry event. (found : %s at %s.%s)", field.getType().getName(), field.getClass().getName(), field.getName()));
|
throw new IllegalStateException(String.format("The ObjectHolder annotation cannot apply to a field that does not map to a registry. Ensure the registry was created during the RegistryEvent.NewRegistry event. (found : %s at %s.%s)", field.getType().getName(), field.getClass().getName(), field.getName()));
|
||||||
}
|
}
|
||||||
try
|
|
||||||
|
field.setAccessible(true);
|
||||||
|
|
||||||
|
if (Modifier.isFinal(field.getModifiers()))
|
||||||
{
|
{
|
||||||
FinalFieldHelper.makeWritable(field);
|
throw new RuntimeException("@ObjectHolder on final field, our transformer did not run? " + field.getDeclaringClass().getName() + "/" + field.getName());
|
||||||
}
|
|
||||||
catch (ReflectiveOperationException e)
|
|
||||||
{
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,46 +135,16 @@ class ObjectHolderRef
|
||||||
|
|
||||||
if (thing == null)
|
if (thing == null)
|
||||||
{
|
{
|
||||||
FMLLog.log.debug("Unable to lookup {} for {}. This means the object wasn't registered. It's likely just mod options.", injectedObject, field);
|
LOGGER.debug("Unable to lookup {} for {}. This means the object wasn't registered. It's likely just mod options.", injectedObject, field);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
FinalFieldHelper.setField(field, null, thing);
|
field.set(null, thing);
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException | ReflectiveOperationException e)
|
catch (IllegalArgumentException | ReflectiveOperationException e)
|
||||||
{
|
{
|
||||||
FMLLog.log.warn("Unable to set {} with value {} ({})", this.field, thing, this.injectedObject, e);
|
LOGGER.warn("Unable to set {} with value {} ({})", this.field, thing, this.injectedObject, e);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FinalFieldHelper
|
|
||||||
{
|
|
||||||
private static Field modifiersField;
|
|
||||||
private static Object reflectionFactory;
|
|
||||||
private static Method newFieldAccessor;
|
|
||||||
private static Method fieldAccessorSet;
|
|
||||||
|
|
||||||
static Field makeWritable(Field f) throws ReflectiveOperationException
|
|
||||||
{
|
|
||||||
f.setAccessible(true);
|
|
||||||
if (modifiersField == null)
|
|
||||||
{
|
|
||||||
Method getReflectionFactory = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("getReflectionFactory");
|
|
||||||
reflectionFactory = getReflectionFactory.invoke(null);
|
|
||||||
newFieldAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newFieldAccessor", Field.class, boolean.class);
|
|
||||||
fieldAccessorSet = Class.forName("sun.reflect.FieldAccessor").getDeclaredMethod("set", Object.class, Object.class);
|
|
||||||
modifiersField = Field.class.getDeclaredField("modifiers");
|
|
||||||
modifiersField.setAccessible(true);
|
|
||||||
}
|
|
||||||
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setField(Field field, @Nullable Object instance, Object thing) throws ReflectiveOperationException
|
|
||||||
{
|
|
||||||
Object fieldAccessor = newFieldAccessor.invoke(reflectionFactory, field, false);
|
|
||||||
fieldAccessorSet.invoke(fieldAccessor, instance, thing);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,66 +26,48 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
import net.minecraft.util.ResourceLocation;
|
||||||
import net.minecraftforge.fml.common.FMLLog;
|
|
||||||
import net.minecraftforge.fml.common.Mod;
|
import net.minecraftforge.fml.common.Mod;
|
||||||
import net.minecraftforge.fml.common.registry.GameRegistry;
|
import net.minecraftforge.fml.common.registry.GameRegistry;
|
||||||
import net.minecraftforge.fml.common.registry.GameRegistry.ObjectHolder;
|
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal registry for tracking {@link ObjectHolder} references
|
* Internal registry for tracking {@link ObjectHolder} references
|
||||||
*/
|
*/
|
||||||
public enum ObjectHolderRegistry
|
public enum ObjectHolderRegistry
|
||||||
{
|
{
|
||||||
INSTANCE;
|
INSTANCE;
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger();
|
||||||
private List<ObjectHolderRef> objectHolders = Lists.newArrayList();
|
private List<ObjectHolderRef> objectHolders = Lists.newArrayList();
|
||||||
|
|
||||||
public void findObjectHolders(ASMDataTable table)
|
public void findObjectHolders(ASMDataTable table)
|
||||||
{
|
{
|
||||||
FMLLog.log.info("Processing ObjectHolder annotations");
|
LOGGER.info("Processing ObjectHolder annotations");
|
||||||
Set<ASMData> allObjectHolders = table.getAll(GameRegistry.ObjectHolder.class.getName());
|
Set<ASMData> allObjectHolders = table.getAll(ObjectHolder.class.getName());
|
||||||
Map<String, String> classModIds = Maps.newHashMap();
|
Map<String, String> classModIds = Maps.newHashMap();
|
||||||
Map<String, Class<?>> classCache = Maps.newHashMap();
|
Map<String, Class<?>> classCache = Maps.newHashMap();
|
||||||
for (ASMData data : table.getAll(Mod.class.getName()))
|
|
||||||
{
|
table.getAll(Mod.class.getName()).forEach(data -> classModIds.put(data.getClassName(), (String)data.getAnnotationInfo().get("value")));
|
||||||
String modid = (String)data.getAnnotationInfo().get("modid");
|
|
||||||
classModIds.put(data.getClassName(), modid);
|
|
||||||
}
|
|
||||||
for (ASMData data : allObjectHolders)
|
|
||||||
{
|
|
||||||
String className = data.getClassName();
|
|
||||||
String annotationTarget = data.getObjectName();
|
|
||||||
String value = (String) data.getAnnotationInfo().get("value");
|
|
||||||
boolean isClass = className.equals(annotationTarget);
|
|
||||||
if (isClass)
|
|
||||||
{
|
|
||||||
scanTarget(classModIds, classCache, className, annotationTarget, value, isClass, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// double pass - get all the class level annotations first, then the field level annotations
|
// double pass - get all the class level annotations first, then the field level annotations
|
||||||
for (ASMData data : allObjectHolders)
|
allObjectHolders.stream().filter(data -> data.getObjectName().equals(data.getClassName())).forEach(data ->
|
||||||
{
|
{
|
||||||
String className = data.getClassName();
|
String value = (String)data.getAnnotationInfo().get("value");
|
||||||
String annotationTarget = data.getObjectName();
|
scanTarget(classModIds, classCache, data.getClassName(), data.getObjectName(), value, true, data.getClassName().startsWith("net.minecraft.init"));
|
||||||
String value = (String) data.getAnnotationInfo().get("value");
|
});
|
||||||
boolean isClass = className.equals(annotationTarget);
|
allObjectHolders.stream().filter(data -> !data.getObjectName().equals(data.getClassName())).forEach(data ->
|
||||||
if (!isClass)
|
{
|
||||||
{
|
String value = (String)data.getAnnotationInfo().get("value");
|
||||||
scanTarget(classModIds, classCache, className, annotationTarget, value, isClass, false);
|
scanTarget(classModIds, classCache, data.getClassName(), data.getObjectName(), value, false, false);
|
||||||
}
|
});
|
||||||
}
|
LOGGER.info("Found {} ObjectHolder annotations", objectHolders.size());
|
||||||
scanTarget(classModIds, classCache, "net.minecraft.init.Blocks", null, "minecraft", true, true);
|
|
||||||
scanTarget(classModIds, classCache, "net.minecraft.init.Items", null, "minecraft", true, true);
|
|
||||||
scanTarget(classModIds, classCache, "net.minecraft.init.MobEffects", null, "minecraft", true, true);
|
|
||||||
scanTarget(classModIds, classCache, "net.minecraft.init.Biomes", null, "minecraft", true, true);
|
|
||||||
scanTarget(classModIds, classCache, "net.minecraft.init.Enchantments", null, "minecraft", true, true);
|
|
||||||
scanTarget(classModIds, classCache, "net.minecraft.init.SoundEvents", null, "minecraft", true, true);
|
|
||||||
scanTarget(classModIds, classCache, "net.minecraft.init.PotionTypes", null, "minecraft", true, true);
|
|
||||||
FMLLog.log.info("Found {} ObjectHolder annotations", objectHolders.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scanTarget(Map<String, String> classModIds, Map<String, Class<?>> classCache, String className, @Nullable String annotationTarget, String value, boolean isClass, boolean extractFromValue)
|
private void scanTarget(Map<String, String> classModIds, Map<String, Class<?>> classCache, String className, @Nullable String annotationTarget, String value, boolean isClass, boolean extractFromValue)
|
||||||
|
@ -119,7 +101,7 @@ public enum ObjectHolderRegistry
|
||||||
String prefix = classModIds.get(className);
|
String prefix = classModIds.get(className);
|
||||||
if (prefix == null)
|
if (prefix == null)
|
||||||
{
|
{
|
||||||
FMLLog.log.warn("Found an unqualified ObjectHolder annotation ({}) without a modid context at {}.{}, ignoring", value, className, annotationTarget);
|
LOGGER.warn("Found an unqualified ObjectHolder annotation ({}) without a modid context at {}.{}, ignoring", value, className, annotationTarget);
|
||||||
throw new IllegalStateException("Unqualified reference to ObjectHolder");
|
throw new IllegalStateException("Unqualified reference to ObjectHolder");
|
||||||
}
|
}
|
||||||
value = prefix + ":" + value;
|
value = prefix + ":" + value;
|
||||||
|
@ -140,11 +122,10 @@ public enum ObjectHolderRegistry
|
||||||
private void scanClassForFields(Map<String, String> classModIds, String className, String value, Class<?> clazz, boolean extractFromExistingValues)
|
private void scanClassForFields(Map<String, String> classModIds, String className, String value, Class<?> clazz, boolean extractFromExistingValues)
|
||||||
{
|
{
|
||||||
classModIds.put(className, value);
|
classModIds.put(className, value);
|
||||||
|
final int flags = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC;
|
||||||
for (Field f : clazz.getFields())
|
for (Field f : clazz.getFields())
|
||||||
{
|
{
|
||||||
int mods = f.getModifiers();
|
if (((f.getModifiers() & flags) != flags) || f.isAnnotationPresent(ObjectHolder.class))
|
||||||
boolean isMatch = Modifier.isPublic(mods) && Modifier.isStatic(mods) && Modifier.isFinal(mods);
|
|
||||||
if (!isMatch || f.isAnnotationPresent(ObjectHolder.class))
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -162,12 +143,9 @@ public enum ObjectHolderRegistry
|
||||||
|
|
||||||
public void applyObjectHolders()
|
public void applyObjectHolders()
|
||||||
{
|
{
|
||||||
FMLLog.log.info("Applying holder lookups");
|
LOGGER.info("Applying holder lookups");
|
||||||
for (ObjectHolderRef ohr : objectHolders)
|
objectHolders.forEach(ObjectHolderRef::apply);
|
||||||
{
|
LOGGER.info("Holder lookups applied");
|
||||||
ohr.apply();
|
|
||||||
}
|
|
||||||
FMLLog.log.info("Holder lookups applied");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,652 +0,0 @@
|
||||||
package paulscode.sound.codecs;
|
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.nio.ShortBuffer;
|
|
||||||
|
|
||||||
import javax.sound.sampled.AudioFormat;
|
|
||||||
|
|
||||||
import paulscode.sound.ICodec;
|
|
||||||
import paulscode.sound.SoundBuffer;
|
|
||||||
import paulscode.sound.SoundSystemConfig;
|
|
||||||
import paulscode.sound.SoundSystemLogger;
|
|
||||||
import ibxm.FastTracker2;
|
|
||||||
import ibxm.IBXM;
|
|
||||||
import ibxm.Module;
|
|
||||||
import ibxm.ProTracker;
|
|
||||||
import ibxm.ScreamTracker3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The CodecIBXM class provides an ICodec interface for reading from MOD/S3M/XM
|
|
||||||
* files via the IBXM library.
|
|
||||||
*<b><i> SoundSystem CodecIBXM Class License:</b></i><br><b><br>
|
|
||||||
* You are free to use this class for any purpose, commercial or otherwise.
|
|
||||||
* You may modify this class or source code, and distribute it any way you
|
|
||||||
* like, provided the following conditions are met:
|
|
||||||
*<br>
|
|
||||||
* 1) You may not falsely claim to be the author of this class or any
|
|
||||||
* unmodified portion of it.
|
|
||||||
*<br>
|
|
||||||
* 2) You may not copyright this class or a modified version of it and then
|
|
||||||
* sue me for copyright infringement.
|
|
||||||
*<br>
|
|
||||||
* 3) If you modify the source code, you must clearly document the changes
|
|
||||||
* made before redistributing the modified source code, so other users know
|
|
||||||
* it is not the original code.
|
|
||||||
*<br>
|
|
||||||
* 4) You are not required to give me credit for this class in any derived
|
|
||||||
* work, but if you do, you must also mention my website:
|
|
||||||
* http://www.paulscode.com
|
|
||||||
*<br>
|
|
||||||
* 5) I the author will not be responsible for any damages (physical,
|
|
||||||
* financial, or otherwise) caused by the use if this class or any portion
|
|
||||||
* of it.
|
|
||||||
*<br>
|
|
||||||
* 6) I the author do not guarantee, warrant, or make any representations,
|
|
||||||
* either expressed or implied, regarding the use of this class or any
|
|
||||||
* portion of it.
|
|
||||||
* <br><br>
|
|
||||||
* Author: Paul Lamb
|
|
||||||
* <br>
|
|
||||||
* http://www.paulscode.com
|
|
||||||
*</b><br><br>
|
|
||||||
*<b>
|
|
||||||
* This software is based on or using the IBXM library available from
|
|
||||||
* http://www.geocities.com/sunet2000/
|
|
||||||
*</b><br><br>
|
|
||||||
*<br><b>
|
|
||||||
* IBXM is copyright (c) 2007, Martin Cameron, and is licensed under the BSD
|
|
||||||
* License.
|
|
||||||
*<br><br>
|
|
||||||
* All rights reserved.
|
|
||||||
*<br><br>
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*<br><br>
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer. Redistributions in binary
|
|
||||||
* form must reproduce the above copyright notice, this list of conditions and
|
|
||||||
* the following disclaimer in the documentation and/or other materials
|
|
||||||
* provided with the distribution. Neither the name of mumart nor the names of
|
|
||||||
* its contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
* <br><br>
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
||||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
* POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
* <br><br><br></b>
|
|
||||||
*/
|
|
||||||
public class CodecIBXM implements ICodec
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Used to return a current value from one of the synchronized
|
|
||||||
* boolean-interface methods.
|
|
||||||
*/
|
|
||||||
private static final boolean GET = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to set the value in one of the synchronized boolean-interface methods.
|
|
||||||
*/
|
|
||||||
private static final boolean SET = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used when a parameter for one of the synchronized boolean-interface methods
|
|
||||||
* is not aplicable.
|
|
||||||
*/
|
|
||||||
private static final boolean XXX = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* True if there is no more data to read in.
|
|
||||||
*/
|
|
||||||
private boolean endOfStream = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* True if the stream has finished initializing.
|
|
||||||
*/
|
|
||||||
private boolean initialized = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format the converted audio will be in.
|
|
||||||
*/
|
|
||||||
private AudioFormat myAudioFormat = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* True if the using library requires data read by this codec to be
|
|
||||||
* reverse-ordered before returning it from methods read() and readAll().
|
|
||||||
*/
|
|
||||||
private boolean reverseBytes = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IBXM decoder.
|
|
||||||
*/
|
|
||||||
private IBXM ibxm;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module instance to be played.
|
|
||||||
*/
|
|
||||||
private Module module;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Duration of the audio (in frames).
|
|
||||||
*/
|
|
||||||
private int songDuration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Audio read position (in frames).
|
|
||||||
*/
|
|
||||||
private int playPosition;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes status messages, warnings, and error messages.
|
|
||||||
*/
|
|
||||||
private SoundSystemLogger logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor: Grabs a handle to the logger.
|
|
||||||
*/
|
|
||||||
public CodecIBXM()
|
|
||||||
{
|
|
||||||
logger = SoundSystemConfig.getLogger();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells this codec when it will need to reverse the byte order of
|
|
||||||
* the data before returning it in the read() and readAll() methods. The
|
|
||||||
* IBXM library produces audio data in a format that some external audio
|
|
||||||
* libraries require to be reversed. Derivatives of the Library and Source
|
|
||||||
* classes for audio libraries which require this type of data to be reversed
|
|
||||||
* will call the reverseByteOrder() method.
|
|
||||||
* @param b True if the calling audio library requires byte-reversal.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void reverseByteOrder( boolean b )
|
|
||||||
{
|
|
||||||
reverseBytes = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares an audio stream to read from. If another stream is already opened,
|
|
||||||
* it will be closed and a new audio stream opened in its place.
|
|
||||||
* @param url URL to an audio file to stream from.
|
|
||||||
* @return False if an error occurred or if end of stream was reached.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean initialize( URL url )
|
|
||||||
{
|
|
||||||
initialized( SET, false );
|
|
||||||
cleanup();
|
|
||||||
|
|
||||||
if( url == null )
|
|
||||||
{
|
|
||||||
errorMessage( "url null in method 'initialize'" );
|
|
||||||
cleanup();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream is = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
is = url.openStream();
|
|
||||||
}
|
|
||||||
catch( IOException ioe )
|
|
||||||
{
|
|
||||||
errorMessage( "Unable to open stream in method 'initialize'" );
|
|
||||||
printStackTrace( ioe );
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ibxm == null )
|
|
||||||
ibxm = new IBXM( 48000 );
|
|
||||||
if( myAudioFormat == null )
|
|
||||||
myAudioFormat = new AudioFormat( 48000, 16, 2, true, true );
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
setModule( loadModule( is ) );
|
|
||||||
}
|
|
||||||
catch( IllegalArgumentException iae )
|
|
||||||
{
|
|
||||||
errorMessage( "Illegal argument in method 'initialize'" );
|
|
||||||
printStackTrace( iae );
|
|
||||||
if( is != null )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
is.close();
|
|
||||||
}
|
|
||||||
catch( IOException ioe )
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
catch( IOException ioe )
|
|
||||||
{
|
|
||||||
errorMessage( "Error loading module in method 'initialize'" );
|
|
||||||
printStackTrace( ioe );
|
|
||||||
if( is != null )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
is.close();
|
|
||||||
}
|
|
||||||
catch( IOException ioe2 )
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( is != null )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
is.close();
|
|
||||||
}
|
|
||||||
catch( IOException ioe )
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
|
|
||||||
endOfStream( SET, false );
|
|
||||||
initialized( SET, true );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns false if the stream is busy initializing.
|
|
||||||
* @return True if steam is initialized.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean initialized()
|
|
||||||
{
|
|
||||||
return initialized( GET, XXX );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads in one stream buffer worth of audio data. See
|
|
||||||
* {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
|
|
||||||
* information about accessing and changing default settings.
|
|
||||||
* @return The audio data wrapped into a SoundBuffer context.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public SoundBuffer read()
|
|
||||||
{
|
|
||||||
if( endOfStream( GET, XXX ) )
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if( module == null )
|
|
||||||
{
|
|
||||||
errorMessage( "Module null in method 'read'" );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to make sure there is an audio format:
|
|
||||||
if( myAudioFormat == null )
|
|
||||||
{
|
|
||||||
errorMessage( "Audio Format null in method 'read'" );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bufferFrameSize = (int) SoundSystemConfig.getStreamingBufferSize()
|
|
||||||
/ 4;
|
|
||||||
|
|
||||||
int frames = songDuration - playPosition;
|
|
||||||
if( frames > bufferFrameSize )
|
|
||||||
frames = bufferFrameSize;
|
|
||||||
|
|
||||||
if( frames <= 0 )
|
|
||||||
{
|
|
||||||
endOfStream( SET, true );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
byte[] outputBuffer = new byte[ frames * 4 ];
|
|
||||||
|
|
||||||
ibxm.get_audio( outputBuffer, frames );
|
|
||||||
|
|
||||||
playPosition += frames;
|
|
||||||
if( playPosition >= songDuration )
|
|
||||||
{
|
|
||||||
endOfStream( SET, true );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reverse the byte order if necessary:
|
|
||||||
if( reverseBytes )
|
|
||||||
reverseBytes( outputBuffer, 0, frames * 4 );
|
|
||||||
|
|
||||||
// Wrap the data into a SoundBuffer:
|
|
||||||
SoundBuffer buffer = new SoundBuffer( outputBuffer, myAudioFormat );
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads in all the audio data from the stream (up to the default
|
|
||||||
* "maximum file size". See
|
|
||||||
* {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
|
|
||||||
* information about accessing and changing default settings.
|
|
||||||
* @return the audio data wrapped into a SoundBuffer context.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public SoundBuffer readAll()
|
|
||||||
{
|
|
||||||
if( module == null )
|
|
||||||
{
|
|
||||||
errorMessage( "Module null in method 'readAll'" );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to make sure there is an audio format:
|
|
||||||
if( myAudioFormat == null )
|
|
||||||
{
|
|
||||||
errorMessage( "Audio Format null in method 'readAll'" );
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bufferFrameSize = (int) SoundSystemConfig.getFileChunkSize()
|
|
||||||
/ 4;
|
|
||||||
|
|
||||||
byte[] outputBuffer = new byte[ bufferFrameSize * 4 ];
|
|
||||||
|
|
||||||
// Buffer to contain the audio data:
|
|
||||||
byte[] fullBuffer = null;
|
|
||||||
// frames of audio data:
|
|
||||||
int frames;
|
|
||||||
// bytes of audio data:
|
|
||||||
int totalBytes = 0;
|
|
||||||
|
|
||||||
while( (!endOfStream(GET, XXX)) &&
|
|
||||||
(totalBytes < SoundSystemConfig.getMaxFileSize()) )
|
|
||||||
{
|
|
||||||
frames = songDuration - playPosition;
|
|
||||||
if( frames > bufferFrameSize )
|
|
||||||
frames = bufferFrameSize;
|
|
||||||
ibxm.get_audio( outputBuffer, frames );
|
|
||||||
totalBytes += (frames * 4);
|
|
||||||
|
|
||||||
fullBuffer = appendByteArrays( fullBuffer, outputBuffer,
|
|
||||||
frames * 4 );
|
|
||||||
|
|
||||||
playPosition += frames;
|
|
||||||
if( playPosition >= songDuration )
|
|
||||||
{
|
|
||||||
endOfStream( SET, true );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reverse the byte order if necessary:
|
|
||||||
if( reverseBytes )
|
|
||||||
reverseBytes( fullBuffer, 0, totalBytes );
|
|
||||||
|
|
||||||
// Wrap the data into a SoundBuffer:
|
|
||||||
SoundBuffer buffer = new SoundBuffer( fullBuffer, myAudioFormat );
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns false if there is still more data available to be read in.
|
|
||||||
* @return True if end of stream was reached.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean endOfStream()
|
|
||||||
{
|
|
||||||
return endOfStream( GET, XXX );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the audio stream and remove references to all instantiated objects.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void cleanup()
|
|
||||||
{
|
|
||||||
// if( ibxm != null )
|
|
||||||
// ibxm.seek( 0 );
|
|
||||||
playPosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the audio format of the data being returned by the read() and
|
|
||||||
* readAll() methods.
|
|
||||||
* @return Information wrapped into an AudioFormat context.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public AudioFormat getAudioFormat()
|
|
||||||
{
|
|
||||||
return myAudioFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes the data in the specified InputStream into an instance of
|
|
||||||
* ibxm.Module.
|
|
||||||
* @param input an InputStream containing the module file to be decoded.
|
|
||||||
* @throws IllegalArgumentException if the data is not recognised as a module file.
|
|
||||||
*/
|
|
||||||
private static Module loadModule( InputStream input )
|
|
||||||
throws IllegalArgumentException, IOException
|
|
||||||
{
|
|
||||||
DataInputStream data_input_stream = new DataInputStream( input );
|
|
||||||
|
|
||||||
// Check if data is in XM format:
|
|
||||||
byte[] xm_header = new byte[ 60 ];
|
|
||||||
data_input_stream.readFully( xm_header );
|
|
||||||
if( FastTracker2.is_xm( xm_header ) )
|
|
||||||
return FastTracker2.load_xm( xm_header, data_input_stream );
|
|
||||||
|
|
||||||
// Check if data is in ScreamTracker 3 format:
|
|
||||||
byte[] s3m_header = new byte[ 96 ];
|
|
||||||
System.arraycopy( xm_header, 0, s3m_header, 0, 60 );
|
|
||||||
data_input_stream.readFully( s3m_header, 60, 36 );
|
|
||||||
if( ScreamTracker3.is_s3m( s3m_header ) )
|
|
||||||
return ScreamTracker3.load_s3m( s3m_header, data_input_stream );
|
|
||||||
|
|
||||||
// Check if data is in ProTracker format:
|
|
||||||
byte[] mod_header = new byte[ 1084 ];
|
|
||||||
System.arraycopy( s3m_header, 0, mod_header, 0, 96 );
|
|
||||||
data_input_stream.readFully( mod_header, 96, 988 );
|
|
||||||
return ProTracker.load_mod( mod_header, data_input_stream );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Module instance to be played.
|
|
||||||
*/
|
|
||||||
private void setModule( Module m )
|
|
||||||
{
|
|
||||||
if( m != null )
|
|
||||||
module = m;
|
|
||||||
ibxm.set_module( module );
|
|
||||||
songDuration = ibxm.calculate_song_duration();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal method for synchronizing access to the boolean 'initialized'.
|
|
||||||
* @param action GET or SET.
|
|
||||||
* @param value New value if action == SET, or XXX if action == GET.
|
|
||||||
* @return True if steam is initialized.
|
|
||||||
*/
|
|
||||||
private synchronized boolean initialized( boolean action, boolean value )
|
|
||||||
{
|
|
||||||
if( action == SET )
|
|
||||||
initialized = value;
|
|
||||||
return initialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal method for synchronizing access to the boolean 'endOfStream'.
|
|
||||||
* @param action GET or SET.
|
|
||||||
* @param value New value if action == SET, or XXX if action == GET.
|
|
||||||
* @return True if end of stream was reached.
|
|
||||||
*/
|
|
||||||
private synchronized boolean endOfStream( boolean action, boolean value )
|
|
||||||
{
|
|
||||||
if( action == SET )
|
|
||||||
endOfStream = value;
|
|
||||||
return endOfStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trims down the size of the array if it is larger than the specified
|
|
||||||
* maximum length.
|
|
||||||
* @param array Array containing audio data.
|
|
||||||
* @param maxLength Maximum size this array may be.
|
|
||||||
* @return New array.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused") //Forge
|
|
||||||
private static byte[] trimArray( byte[] array, int maxLength )
|
|
||||||
{
|
|
||||||
byte[] trimmedArray = null;
|
|
||||||
if( array != null && array.length > maxLength )
|
|
||||||
{
|
|
||||||
trimmedArray = new byte[maxLength];
|
|
||||||
System.arraycopy( array, 0, trimmedArray, 0, maxLength );
|
|
||||||
}
|
|
||||||
return trimmedArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse-orders all bytes contained in the specified array.
|
|
||||||
* @param buffer Array containing audio data.
|
|
||||||
*/
|
|
||||||
public static void reverseBytes( byte[] buffer )
|
|
||||||
{
|
|
||||||
reverseBytes( buffer, 0, buffer.length );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse-orders the specified range of bytes contained in the specified array.
|
|
||||||
* @param buffer Array containing audio data.
|
|
||||||
* @param offset Array index to begin.
|
|
||||||
* @param size number of bytes to reverse-order.
|
|
||||||
*/
|
|
||||||
public static void reverseBytes( byte[] buffer, int offset, int size )
|
|
||||||
{
|
|
||||||
|
|
||||||
byte b;
|
|
||||||
for( int i = offset; i < ( offset + size ); i += 2 )
|
|
||||||
{
|
|
||||||
b = buffer[i];
|
|
||||||
buffer[i] = buffer[i + 1];
|
|
||||||
buffer[i + 1] = b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts sound bytes to little-endian format.
|
|
||||||
* @param audio_bytes The original wave data
|
|
||||||
* @param two_bytes_data For stereo sounds.
|
|
||||||
* @return byte array containing the converted data.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused") //Forge
|
|
||||||
private static byte[] convertAudioBytes( byte[] audio_bytes,
|
|
||||||
boolean two_bytes_data )
|
|
||||||
{
|
|
||||||
ByteBuffer dest = ByteBuffer.allocateDirect( audio_bytes.length );
|
|
||||||
dest.order( ByteOrder.nativeOrder() );
|
|
||||||
ByteBuffer src = ByteBuffer.wrap( audio_bytes );
|
|
||||||
src.order( ByteOrder.LITTLE_ENDIAN );
|
|
||||||
if( two_bytes_data )
|
|
||||||
{
|
|
||||||
ShortBuffer dest_short = dest.asShortBuffer();
|
|
||||||
ShortBuffer src_short = src.asShortBuffer();
|
|
||||||
while( src_short.hasRemaining() )
|
|
||||||
{
|
|
||||||
dest_short.put(src_short.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
while( src.hasRemaining() )
|
|
||||||
{
|
|
||||||
dest.put( src.get() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dest.rewind();
|
|
||||||
|
|
||||||
if( !dest.hasArray() )
|
|
||||||
{
|
|
||||||
byte[] arrayBackedBuffer = new byte[dest.capacity()];
|
|
||||||
dest.get( arrayBackedBuffer );
|
|
||||||
dest.clear();
|
|
||||||
|
|
||||||
return arrayBackedBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dest.array();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new array with the second array appended to the end of the first
|
|
||||||
* array.
|
|
||||||
* @param arrayOne The first array.
|
|
||||||
* @param arrayTwo The second array.
|
|
||||||
* @param length How many bytes to append from the second array.
|
|
||||||
* @return Byte array containing information from both arrays.
|
|
||||||
*/
|
|
||||||
private static byte[] appendByteArrays( byte[] arrayOne, byte[] arrayTwo,
|
|
||||||
int length )
|
|
||||||
{
|
|
||||||
byte[] newArray;
|
|
||||||
if( arrayOne == null && arrayTwo == null )
|
|
||||||
{
|
|
||||||
// no data, just return
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else if( arrayOne == null )
|
|
||||||
{
|
|
||||||
// create the new array, same length as arrayTwo:
|
|
||||||
newArray = new byte[ length ];
|
|
||||||
// fill the new array with the contents of arrayTwo:
|
|
||||||
System.arraycopy( arrayTwo, 0, newArray, 0, length );
|
|
||||||
arrayTwo = null;
|
|
||||||
}
|
|
||||||
else if( arrayTwo == null )
|
|
||||||
{
|
|
||||||
// create the new array, same length as arrayOne:
|
|
||||||
newArray = new byte[ arrayOne.length ];
|
|
||||||
// fill the new array with the contents of arrayOne:
|
|
||||||
System.arraycopy( arrayOne, 0, newArray, 0, arrayOne.length );
|
|
||||||
arrayOne = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// create the new array large enough to hold both arrays:
|
|
||||||
newArray = new byte[ arrayOne.length + length ];
|
|
||||||
System.arraycopy( arrayOne, 0, newArray, 0, arrayOne.length );
|
|
||||||
// fill the new array with the contents of both arrays:
|
|
||||||
System.arraycopy( arrayTwo, 0, newArray, arrayOne.length,
|
|
||||||
length );
|
|
||||||
arrayOne = null;
|
|
||||||
arrayTwo = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints an error message.
|
|
||||||
* @param message Message to print.
|
|
||||||
*/
|
|
||||||
private void errorMessage( String message )
|
|
||||||
{
|
|
||||||
logger.errorMessage( "CodecWav", message, 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints an exception's error message followed by the stack trace.
|
|
||||||
* @param e Exception containing the information to print.
|
|
||||||
*/
|
|
||||||
private void printStackTrace( Exception e )
|
|
||||||
{
|
|
||||||
logger.printStackTrace( e, 1 );
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +1,3 @@
|
||||||
net.minecraftforge.fml.loading.RuntimeDistCleaner
|
net.minecraftforge.fml.loading.RuntimeDistCleaner
|
||||||
|
net.minecraftforge.common.asm.RuntimeEnumExtener
|
||||||
|
net.minecraftforge.common.asm.ObjectHolderDefinalize
|
||||||
|
|
|
@ -80,3 +80,5 @@ net/minecraft/command/EntitySelector.isSelectorDefault(Ljava/lang/String;)Z=|p_8
|
||||||
net/minecraft/client/util/RecipeItemHelper.accountStack(Lnet/minecraft/item/ItemStack;I)V=|p_194112_1_,forceCount
|
net/minecraft/client/util/RecipeItemHelper.accountStack(Lnet/minecraft/item/ItemStack;I)V=|p_194112_1_,forceCount
|
||||||
net/minecraft/client/renderer/ActiveRenderInfo.updateRenderInfo(Lnet/minecraft/entity/Entity;Z)V=|p_74583_0_,p_74583_1_
|
net/minecraft/client/renderer/ActiveRenderInfo.updateRenderInfo(Lnet/minecraft/entity/Entity;Z)V=|p_74583_0_,p_74583_1_
|
||||||
net/minecraft/client/audio/SoundManager.setListener(Lnet/minecraft/entity/Entity;F)V=|p_148615_1_,p_148615_2_
|
net/minecraft/client/audio/SoundManager.setListener(Lnet/minecraft/entity/Entity;F)V=|p_148615_1_,p_148615_2_
|
||||||
|
|
||||||
|
net/minecraft/item/ArmorMaterial.create(Ljava/lang/String;Ljava/lang/String;I[IILnet/minecraft/util/SoundEvent;FLjava/util/function/Supplier;)V=|name,p_i48533_3_,p_i48533_4_,p_i48533_5_,p_i48533_6_,p_i48533_7_,p_i48533_8_,p_i48533_9_
|
||||||
|
|
Loading…
Reference in a new issue