mirror of
https://github.com/collinsmith/riiablo.git
synced 2025-01-20 09:17:29 +07:00
Refactored com.riiablo.item to use com.riiablo.io for data streams
Temp workarounds implemented for Item#loadFromStream and ItemSerializer#readSingleItem
This commit is contained in:
parent
cafe8679b4
commit
c0ef2dcb8a
@ -1,5 +1,10 @@
|
||||
package com.riiablo.io;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.riiablo.util.DebugUtils;
|
||||
|
||||
public class BitUtils {
|
||||
private BitUtils() {}
|
||||
|
||||
@ -24,4 +29,27 @@ public class BitUtils {
|
||||
public static boolean isUnsigned(long value) {
|
||||
return isUnsigned(value, Long.SIZE);
|
||||
}
|
||||
|
||||
public static boolean readSignature(ByteInput in, final byte[] SIGNATURE, Logger log, String tag) {
|
||||
log.trace("Validating " + tag + " signature");
|
||||
if (in.bytesRemaining() < SIGNATURE.length) {
|
||||
byte[] signature = in.readBytes(in.bytesRemaining());
|
||||
throw new InvalidFormat(
|
||||
in,
|
||||
String.format(tag + " signature doesn't match expected signature: %s, expected %s",
|
||||
DebugUtils.toByteArray(signature),
|
||||
DebugUtils.toByteArray(SIGNATURE)));
|
||||
}
|
||||
|
||||
byte[] signature = in.readBytes(SIGNATURE.length);
|
||||
boolean matched = Arrays.equals(signature, SIGNATURE);
|
||||
if (!matched) {
|
||||
throw new InvalidFormat(
|
||||
in,
|
||||
String.format(tag + " signature doesn't match expected signature: %s, expected %s",
|
||||
DebugUtils.toByteArray(signature),
|
||||
DebugUtils.toByteArray(SIGNATURE)));
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
}
|
||||
|
@ -173,6 +173,18 @@ public class ByteInput {
|
||||
return new ByteInput(slice, mark);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array containing a duplicated region of the byte stream.
|
||||
*
|
||||
* @deprecated will be removed
|
||||
*/
|
||||
@Deprecated
|
||||
public byte[] duplicate(int offset, int len) {
|
||||
byte[] dst = new byte[len];
|
||||
buffer.getBytes(offset, dst);
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next byte from the byte stream, ignoring alignment.
|
||||
*/
|
||||
|
@ -19,6 +19,7 @@ import com.riiablo.codec.excel.SetItems;
|
||||
import com.riiablo.codec.excel.UniqueItems;
|
||||
import com.riiablo.codec.excel.Weapons;
|
||||
import com.riiablo.codec.util.BitStream;
|
||||
import com.riiablo.io.ByteInput;
|
||||
|
||||
public class Item {
|
||||
private static final String TAG = "Item";
|
||||
@ -32,8 +33,14 @@ public class Item {
|
||||
private static final ItemSerializer DEFAULT_SERIALIZER = new ItemSerializer();
|
||||
private static final ItemLabeler DEFAULT_LABELER = new ItemLabeler();
|
||||
|
||||
/**
|
||||
* @deprecated use {@link ItemSerializer#readItem(ByteInput)} instead!
|
||||
*/
|
||||
@Deprecated
|
||||
public static Item loadFromStream(BitStream bitStream) {
|
||||
return DEFAULT_SERIALIZER.readSingleItem(bitStream);
|
||||
// FIXME: workaround for BitStream backcompat
|
||||
ByteInput bitInput = ByteInput.wrap(bitStream.getBufferAtPos());
|
||||
return DEFAULT_SERIALIZER.readSingleItem(bitInput);
|
||||
}
|
||||
|
||||
public static final float ETHEREAL_ALPHA = 2 / 3f;
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.riiablo.item;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
@ -8,77 +7,78 @@ import com.badlogic.gdx.utils.Array;
|
||||
import com.riiablo.Riiablo;
|
||||
import com.riiablo.codec.excel.Gems;
|
||||
import com.riiablo.codec.util.BitStream;
|
||||
import com.riiablo.io.InvalidFormat;
|
||||
import com.riiablo.io.BitInput;
|
||||
import com.riiablo.io.BitUtils;
|
||||
import com.riiablo.io.ByteInput;
|
||||
import com.riiablo.log.Log;
|
||||
import com.riiablo.log.LogManager;
|
||||
import com.riiablo.util.DebugUtils;
|
||||
|
||||
public class ItemSerializer {
|
||||
private static final Logger log = LogManager.getLogger(ItemSerializer.class);
|
||||
|
||||
private static final byte[] SIGNATURE = {0x4A, 0x4D};
|
||||
|
||||
private static boolean readSignature(BitStream bitStream) {
|
||||
log.trace("Validating item signature");
|
||||
byte[] signature = bitStream.readFully(SIGNATURE.length);
|
||||
boolean matched = Arrays.equals(signature, SIGNATURE);
|
||||
if (!matched) {
|
||||
throw new InvalidFormat(
|
||||
String.format("Item signature doesn't match expected signature: %s, expected %s",
|
||||
DebugUtils.toByteArray(signature),
|
||||
DebugUtils.toByteArray(SIGNATURE)));
|
||||
}
|
||||
return matched;
|
||||
public void skipUntil(ByteInput in) {
|
||||
in.skipUntil(SIGNATURE);
|
||||
}
|
||||
|
||||
public Item readItem(BitStream bitStream) {
|
||||
Item item = readSingleItem(bitStream);
|
||||
public Item readItem(ByteInput in) {
|
||||
final int itemOffset = in.bytesRead(); // TODO: remove when serialization implemented
|
||||
Item item = readSingleItem(in);
|
||||
if (item.socketsFilled > 0) log.trace("Reading {} sockets...", item.socketsFilled);
|
||||
for (int i = 0; i < item.socketsFilled; i++) {
|
||||
try {
|
||||
Log.put("socket", String.valueOf(i));
|
||||
bitStream.alignToByte();
|
||||
item.sockets.add(readSingleItem(bitStream));
|
||||
in.skipUntil(SIGNATURE);
|
||||
item.sockets.add(readSingleItem(in));
|
||||
} finally {
|
||||
Log.remove("socket");
|
||||
}
|
||||
}
|
||||
final int itemSize = in.bytesRead() - itemOffset; // TODO: remove when serialization implemented
|
||||
item.data = in.duplicate(itemOffset, itemSize); // TODO: remove when serialization implemented
|
||||
return item;
|
||||
}
|
||||
|
||||
public Item readSingleItem(BitStream bitStream) {
|
||||
public Item readSingleItem(ByteInput in) {
|
||||
/** FIXME: workaround for {@link Item#loadFromStream(BitStream)} */
|
||||
final int itemOffset = in.bytesRead(); // TODO: remove when serialization implemented
|
||||
log.trace("Reading item...");
|
||||
readSignature(bitStream);
|
||||
BitUtils.readSignature(in, SIGNATURE, log, "item");
|
||||
Item item = new Item();
|
||||
item.reset();
|
||||
item.flags = (int) bitStream.readRaw(32);
|
||||
item.flags = in.read32();
|
||||
Log.tracef(log, "flags: 0x%08X [%s]", item.flags, item.getFlagsString());
|
||||
item.version = bitStream.readU31(8);
|
||||
item.version = in.readSafe8u();
|
||||
log.trace("version: {}", item.version);
|
||||
bitStream.skip(2); // Unknown use -- safe to skip
|
||||
item.location = Location.valueOf(bitStream.readU7(3));
|
||||
item.bodyLoc = BodyLoc.valueOf(bitStream.readU7(4));
|
||||
item.gridX = bitStream.readU7(4);
|
||||
item.gridY = bitStream.readU7(4);
|
||||
item.storeLoc = StoreLoc.valueOf(bitStream.readU7(3));
|
||||
final BitInput bits = in.unalign();
|
||||
bits.skipBits(2); // Unknown use -- safe to skip
|
||||
item.location = Location.valueOf(bits.read7u(3));
|
||||
item.bodyLoc = BodyLoc.valueOf(bits.read7u(4));
|
||||
item.gridX = bits.read7u(4);
|
||||
item.gridY = bits.read7u(4);
|
||||
item.storeLoc = StoreLoc.valueOf(bits.read7u(3));
|
||||
|
||||
if ((item.flags & Item.ITEMFLAG_BODYPART) == Item.ITEMFLAG_BODYPART) {
|
||||
int charClass = bitStream.readU7(3);
|
||||
int charLevel = bitStream.readU7(7);
|
||||
String charName = bitStream.readString2(Riiablo.MAX_NAME_LENGTH + 1, 7);
|
||||
int charClass = bits.read7u(3);
|
||||
int charLevel = bits.read7u(7);
|
||||
String charName = bits.readString(Riiablo.MAX_NAME_LENGTH + 1, 7, true);
|
||||
item.setEar(charClass, charLevel, charName);
|
||||
} else {
|
||||
item.setBase(bitStream.readString(4).trim());
|
||||
item.socketsFilled = bitStream.readU7(3);
|
||||
item.setBase(bits.readString(4).trim());
|
||||
item.socketsFilled = bits.read7u(3);
|
||||
}
|
||||
|
||||
log.trace("code: {}", item.code);
|
||||
if ((item.flags & Item.ITEMFLAG_COMPACT) == Item.ITEMFLAG_COMPACT) {
|
||||
readCompact(item);
|
||||
} else {
|
||||
readStandard(bitStream, item);
|
||||
readStandard(bits, item);
|
||||
}
|
||||
|
||||
bits.align();
|
||||
final int itemSize = in.bytesRead() - itemOffset; // TODO: remove when serialization implemented
|
||||
item.data = in.duplicate(itemOffset, itemSize); // TODO: remove when serialization implemented
|
||||
return item;
|
||||
}
|
||||
|
||||
@ -91,46 +91,46 @@ public class ItemSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void readStandard(BitStream bitStream, Item item) {
|
||||
item.data = bitStream.getBufferView(); // TODO: remove when serialization implemented
|
||||
item.id = (int) bitStream.readRaw(32);
|
||||
private static void readStandard(BitInput bits, Item item) {
|
||||
item.id = (int) bits.readRaw(32);
|
||||
Log.tracef(log, "id: 0x%08X", item.id);
|
||||
item.ilvl = bitStream.readU7(7);
|
||||
item.quality = Quality.valueOf(bitStream.readU7(4));
|
||||
item.pictureId = bitStream.readBoolean() ? bitStream.readU7(3) : Item.NO_PICTURE_ID;
|
||||
item.classOnly = bitStream.readBoolean() ? bitStream.readU15(11) : Item.NO_CLASS_ONLY;
|
||||
readQualityData(bitStream, item);
|
||||
item.ilvl = bits.read7u(7);
|
||||
item.quality = Quality.valueOf(bits.read7u(4));
|
||||
item.pictureId = bits.readBoolean() ? bits.read7u(3) : Item.NO_PICTURE_ID;
|
||||
item.classOnly = bits.readBoolean() ? bits.read15u(11) : Item.NO_CLASS_ONLY;
|
||||
readQualityData(bits, item);
|
||||
|
||||
int listFlags = Item.MAGIC_PROPS_FLAG;
|
||||
if (readRunewordData(bitStream, item)) listFlags |= Item.RUNE_PROPS_FLAG;
|
||||
if (readRunewordData(bits, item)) listFlags |= Item.RUNE_PROPS_FLAG;
|
||||
|
||||
item.inscription = (item.flags & Item.ITEMFLAG_INSCRIBED) == Item.ITEMFLAG_INSCRIBED
|
||||
? bitStream.readString2(Riiablo.MAX_NAME_LENGTH + 1, 7) : null;
|
||||
? bits.readString(Riiablo.MAX_NAME_LENGTH + 1, 7, true)
|
||||
: null;
|
||||
|
||||
bitStream.skip(1); // TODO: Unknown, this usually is 0, but is 1 on a Tome of Identify. (It's still 0 on a Tome of Townportal.)
|
||||
bits.skipBits(1); // TODO: Unknown, this usually is 0, but is 1 on a Tome of Identify. (It's still 0 on a Tome of Townportal.)
|
||||
|
||||
readArmorClass(bitStream, item);
|
||||
readDurability(bitStream, item);
|
||||
readSockets(bitStream, item);
|
||||
readBook(bitStream, item);
|
||||
readQuantity(bitStream, item);
|
||||
readArmorClass(bits, item);
|
||||
readDurability(bits, item);
|
||||
readSockets(bits, item);
|
||||
readBook(bits, item);
|
||||
readQuantity(bits, item);
|
||||
|
||||
if (item.type.is(Type.BOOK)) listFlags = 0;
|
||||
listFlags |= (readSetFlags(bitStream, item) << Item.SET_PROPS);
|
||||
listFlags |= (readSetFlags(bits, item) << Item.SET_PROPS);
|
||||
PropertyList[] props = item.stats = new PropertyList[Item.NUM_PROPS];
|
||||
for (int i = 0; i < Item.NUM_PROPS; i++) {
|
||||
if (((listFlags >> i) & 1) == 1) {
|
||||
props[i] = PropertyList.obtain().read(bitStream);
|
||||
props[i] = PropertyList.obtain().read(bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean readQualityData(BitStream bitStream, Item item) {
|
||||
private static boolean readQualityData(BitInput bits, Item item) {
|
||||
log.trace("quality: {}", item.quality);
|
||||
switch (item.quality) {
|
||||
case LOW:
|
||||
case HIGH:
|
||||
item.qualityId = bitStream.readU31(3);
|
||||
item.qualityId = bits.read31u(3);
|
||||
log.trace("qualityId: {}", item.qualityId);
|
||||
return true;
|
||||
|
||||
@ -139,7 +139,7 @@ public class ItemSerializer {
|
||||
return true;
|
||||
|
||||
case SET:
|
||||
item.qualityId = bitStream.readU31(Item.SET_ID_SIZE);
|
||||
item.qualityId = bits.read31u(Item.SET_ID_SIZE);
|
||||
log.trace("qualityId: {}", item.qualityId);
|
||||
item.qualityData = Riiablo.files.SetItems.get(item.qualityId);
|
||||
log.trace("qualityData: {}", item.qualityData);
|
||||
@ -151,7 +151,7 @@ public class ItemSerializer {
|
||||
return true;
|
||||
|
||||
case UNIQUE:
|
||||
item.qualityId = bitStream.readU31(Item.UNIQUE_ID_SIZE);
|
||||
item.qualityId = bits.read31u(Item.UNIQUE_ID_SIZE);
|
||||
log.trace("qualityId: {}", item.qualityId);
|
||||
item.qualityData = Riiablo.files.UniqueItems.get(item.qualityId);
|
||||
log.trace("qualityData: {}", item.qualityData);
|
||||
@ -163,15 +163,15 @@ public class ItemSerializer {
|
||||
return true;
|
||||
|
||||
case MAGIC:
|
||||
item.qualityId = bitStream.readU31(2 * Item.MAGIC_AFFIX_SIZE); // 11 for prefix, 11 for suffix
|
||||
item.qualityId = bits.read31u(2 * Item.MAGIC_AFFIX_SIZE); // 11 for prefix, 11 for suffix
|
||||
log.trace("qualityId: {}", item.qualityId);
|
||||
return true;
|
||||
|
||||
case RARE:
|
||||
case CRAFTED:
|
||||
item.qualityId = bitStream.readU31(2 * Item.RARE_AFFIX_SIZE); // 8 for prefix, 8 for suffix
|
||||
item.qualityId = bits.read31u(2 * Item.RARE_AFFIX_SIZE); // 8 for prefix, 8 for suffix
|
||||
log.trace("qualityId: {}", item.qualityId);
|
||||
item.qualityData = new RareQualityData(bitStream);
|
||||
item.qualityData = new RareQualityData(bits);
|
||||
log.trace("qualityData: {}", item.qualityData);
|
||||
return true;
|
||||
|
||||
@ -181,51 +181,51 @@ public class ItemSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
private static int readSetFlags(BitStream bitStream, Item item) {
|
||||
return item.quality == Quality.SET ? bitStream.readU7(5) : 0;
|
||||
private static int readSetFlags(BitInput bits, Item item) {
|
||||
return item.quality == Quality.SET ? bits.read7u(5) : 0;
|
||||
}
|
||||
|
||||
private static boolean readRunewordData(BitStream bitStream, Item item) {
|
||||
private static boolean readRunewordData(BitInput bits, Item item) {
|
||||
boolean hasRunewordData = (item.flags & Item.ITEMFLAG_RUNEWORD) == Item.ITEMFLAG_RUNEWORD;
|
||||
item.runewordData = hasRunewordData ? (short) bitStream.readRaw(16) : 0;
|
||||
item.runewordData = hasRunewordData ? (short) bits.readRaw(16) : 0;
|
||||
return hasRunewordData;
|
||||
}
|
||||
|
||||
private static boolean readArmorClass(BitStream bitStream, Item item) {
|
||||
private static boolean readArmorClass(BitInput bits, Item item) {
|
||||
boolean hasAC = item.type.is(Type.ARMO);
|
||||
if (hasAC) item.props.base().read(Stat.armorclass, bitStream);
|
||||
if (hasAC) item.props.base().read(Stat.armorclass, bits);
|
||||
return hasAC;
|
||||
}
|
||||
|
||||
private static boolean readDurability(BitStream bitStream, Item item) {
|
||||
private static boolean readDurability(BitInput bits, Item item) {
|
||||
boolean hasDurability = item.type.is(Type.ARMO) || item.type.is(Type.WEAP);
|
||||
if (hasDurability) {
|
||||
int maxdurability = item.props.base().read(Stat.maxdurability, bitStream);
|
||||
if (maxdurability > 0) item.props.base().read(Stat.durability, bitStream);
|
||||
int maxdurability = item.props.base().read(Stat.maxdurability, bits);
|
||||
if (maxdurability > 0) item.props.base().read(Stat.durability, bits);
|
||||
}
|
||||
return hasDurability;
|
||||
}
|
||||
|
||||
private static boolean readSockets(BitStream bitStream, Item item) {
|
||||
private static boolean readSockets(BitInput bits, Item item) {
|
||||
boolean hasSockets = (item.flags & Item.ITEMFLAG_SOCKETED) == Item.ITEMFLAG_SOCKETED
|
||||
&& (item.type.is(Type.ARMO) || item.type.is(Type.WEAP));
|
||||
if (hasSockets) {
|
||||
int item_numsockets = item.props.base().read(Stat.item_numsockets, bitStream);
|
||||
int item_numsockets = item.props.base().read(Stat.item_numsockets, bits);
|
||||
item.sockets = new Array<>(item_numsockets);
|
||||
}
|
||||
return hasSockets;
|
||||
}
|
||||
|
||||
private static boolean readBook(BitStream bitStream, Item item) {
|
||||
private static boolean readBook(BitInput bits, Item item) {
|
||||
boolean isBook = item.type.is(Type.BOOK);
|
||||
if (isBook) bitStream.skip(5); // TODO: Appears to be 0 for tbk and 1 for ibk
|
||||
if (isBook) bits.skipBits(5); // TODO: Appears to be 0 for tbk and 1 for ibk
|
||||
return isBook;
|
||||
}
|
||||
|
||||
private static boolean readQuantity(BitStream bitStream, Item item) {
|
||||
private static boolean readQuantity(BitInput bits, Item item) {
|
||||
boolean hasQuantity = item.base.stackable;
|
||||
if (hasQuantity) {
|
||||
int quantity = bitStream.readU15(9);
|
||||
int quantity = bits.read15u(9);
|
||||
item.props.base().put(Stat.quantity, quantity);
|
||||
}
|
||||
return hasQuantity;
|
||||
|
@ -10,7 +10,7 @@ import com.badlogic.gdx.utils.IntMap;
|
||||
import com.riiablo.Riiablo;
|
||||
import com.riiablo.codec.excel.ItemStatCost;
|
||||
import com.riiablo.codec.excel.Properties;
|
||||
import com.riiablo.codec.util.BitStream;
|
||||
import com.riiablo.io.BitInput;
|
||||
|
||||
public class PropertyList implements Iterable<Stat> {
|
||||
private static final String TAG = "PropertyList";
|
||||
@ -71,14 +71,14 @@ public class PropertyList implements Iterable<Stat> {
|
||||
return props.entries().next().value;
|
||||
}
|
||||
|
||||
public int read(int stat, BitStream bitStream) {
|
||||
public int read(int stat, BitInput bitStream) {
|
||||
Stat instance = Stat.obtain(stat, bitStream);
|
||||
props.put(instance.hash, instance);
|
||||
return instance.val;
|
||||
}
|
||||
|
||||
public PropertyList read(BitStream bitStream) {
|
||||
for (int prop; (prop = bitStream.readU15(Stat.BITS)) != Stat.NONE;) {
|
||||
public PropertyList read(BitInput bitStream) {
|
||||
for (int prop; (prop = bitStream.read15u(Stat.BITS)) != Stat.NONE;) {
|
||||
for (int j = prop, size = j + Stat.getNumEncoded(prop); j < size; j++) {
|
||||
read(j, bitStream);
|
||||
}
|
||||
|
@ -2,17 +2,17 @@ package com.riiablo.item;
|
||||
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import com.riiablo.codec.util.BitStream;
|
||||
import com.riiablo.io.BitInput;
|
||||
|
||||
class RareQualityData {
|
||||
static final int NUM_AFFIXES = 3;
|
||||
int[] prefixes, suffixes;
|
||||
RareQualityData(BitStream bitStream) {
|
||||
RareQualityData(BitInput bitStream) {
|
||||
prefixes = new int[NUM_AFFIXES];
|
||||
suffixes = new int[NUM_AFFIXES];
|
||||
for (int i = 0; i < NUM_AFFIXES; i++) {
|
||||
prefixes[i] = bitStream.readBoolean() ? bitStream.readU15(Item.MAGIC_AFFIX_SIZE) : 0;
|
||||
suffixes[i] = bitStream.readBoolean() ? bitStream.readU15(Item.MAGIC_AFFIX_SIZE) : 0;
|
||||
prefixes[i] = bitStream.readBoolean() ? bitStream.read15u(Item.MAGIC_AFFIX_SIZE) : 0;
|
||||
suffixes[i] = bitStream.readBoolean() ? bitStream.read15u(Item.MAGIC_AFFIX_SIZE) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import com.riiablo.codec.excel.CharStats;
|
||||
import com.riiablo.codec.excel.ItemStatCost;
|
||||
import com.riiablo.codec.excel.SkillDesc;
|
||||
import com.riiablo.codec.excel.Skills;
|
||||
import com.riiablo.codec.util.BitStream;
|
||||
import com.riiablo.io.BitInput;
|
||||
import com.riiablo.save.CharData;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ -459,7 +459,7 @@ public class Stat implements Comparable<Stat> {
|
||||
return new Stat(); // POOL.obtain();
|
||||
}
|
||||
|
||||
static Stat obtain(int stat, BitStream bitStream) {
|
||||
static Stat obtain(int stat, BitInput bitStream) {
|
||||
return obtain()._obtain(stat, bitStream);
|
||||
}
|
||||
|
||||
@ -484,11 +484,11 @@ public class Stat implements Comparable<Stat> {
|
||||
|
||||
Stat() {}
|
||||
|
||||
Stat _obtain(int stat, BitStream bitStream) {
|
||||
Stat _obtain(int stat, BitInput bitStream) {
|
||||
this.id = stat;
|
||||
entry = Riiablo.files.ItemStatCost.get(stat);
|
||||
param = bitStream.readU31(entry.Save_Param_Bits);
|
||||
val = bitStream.readU31(entry.Save_Bits) - entry.Save_Add;
|
||||
param = bitStream.read31u(entry.Save_Param_Bits);
|
||||
val = bitStream.read31u(entry.Save_Bits) - entry.Save_Add;
|
||||
hash = hash(stat, param);
|
||||
modified = false;
|
||||
return this;
|
||||
|
Loading…
Reference in New Issue
Block a user