diff --git a/src/io/ByteReader.java b/src/io/ByteReader.java
index 495a8b676e09b99ace0d02134f800650abb09188..96a30fb5e4979a4c30a2d4a9527ae666e69055f2 100644
--- a/src/io/ByteReader.java
+++ b/src/io/ByteReader.java
@@ -44,6 +44,13 @@ public class ByteReader extends InputStream {
 		return pos;
 	}
 	
+	public boolean seek(int n) throws IOException {
+		if (n == 0) return false;
+		for (int i=0; i<n; i++)
+			read();		
+		return true;
+	}
+	
 	@Override
 	public int read() throws IOException {
 		pos++;
@@ -133,6 +140,34 @@ public class ByteReader extends InputStream {
 		else 
 			return b7 | (b6 << 8) | (b5 << 16) | (b4 << 24) | (b3 << 32) | (b2 << 40) | (b1 << 48) | (b0 << 56);
 	}
+	
+	public String readString(int start, int size) throws IOException {
+		int sz = size + start - getPosition();
+		if (sz == 0) return null;
+		byte[] buf = new byte[sz];
+		int i = 0;
+		for (;; i++) {
+			int b = read();
+			if (b == 0) break;
+			buf[i] = (byte)b;
+		}
+		if (i == 0) return null;
+		return new String(buf, 0, i);
+	}
+	
+	public String readUnicodeString(int start, int size) throws IOException {
+		int sz = (size + start - getPosition())>>1;
+		if (sz == 0) return null;
+		char[] buf = new char[sz];		
+		int i = 0;
+		for (;; i++) {
+			char c = (char)read2bytes();
+			if (c == 0) break;
+			buf[i] = c;
+		}
+		if (i == 0) return null;
+		return new String(buf, 0, i);
+	}
 }
 
 enum Endianness {
diff --git a/src/io/ByteWriter.java b/src/io/ByteWriter.java
index ddbe31b6b24aee2b96054be3a9188144224fe1db..ad6ecf6f8140f7ca980d6898211a98312ec30c38 100644
--- a/src/io/ByteWriter.java
+++ b/src/io/ByteWriter.java
@@ -7,6 +7,8 @@ public class ByteWriter extends OutputStream {
 
 	private OutputStream stream;
 	private Endianness end = Endianness.LITTLE_ENDIAN;
+	private int pos = 0;
+	
 	
 	public ByteWriter(OutputStream out) {
 		stream = out;
@@ -38,8 +40,13 @@ public class ByteWriter extends OutputStream {
 		return end == Endianness.LITTLE_ENDIAN;
 	}
 	
+	public int getPosition() {
+		return pos;
+	}
+	
 	@Override
 	public void write(int b) throws IOException {
+		pos++;
 		stream.write(b);
 	}
 	
@@ -137,4 +144,9 @@ public class ByteWriter extends OutputStream {
 			write(b7); write(b6); write(b5); write(b4); write(b3); write(b2); write(b1); write(b0);
 		}
 	}
+	
+	public void writeBytes(byte[] b) throws IOException {
+		for (byte i : b) 
+			write(i);
+	}
 }
diff --git a/src/io/Bytes.java b/src/io/Bytes.java
index 4abadf9d75631d92cfbddddd8af6763f01c99000..b322dc8e32a156af598b4bcd972f1610062907a2 100644
--- a/src/io/Bytes.java
+++ b/src/io/Bytes.java
@@ -39,12 +39,12 @@ public class Bytes {
 		return (Bytes.l(b7) << 56) | (Bytes.l(b6) << 48) | (Bytes.l(b5) << 40) | (Bytes.l(b4) << 32) | (Bytes.l(b3) << 24) | (Bytes.l(b2) << 16) | (Bytes.l(b1) << 8) | Bytes.l(b0);
 	}
 
-	static int i(byte b) {
-		return b & 0xff;
-	}
-
 	static long l(byte b) {
 		return b & 0xffL;
 	}
 
+	static int i(byte b) {
+		return b & 0xff;
+	}
+
 }
diff --git a/src/mslinks/LinkInfo.java b/src/mslinks/LinkInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..abb3c0536bd97041b9a4b6e9f7a377d94a1b338d
--- /dev/null
+++ b/src/mslinks/LinkInfo.java
@@ -0,0 +1,232 @@
+package mslinks;
+
+import io.ByteReader;
+import io.ByteWriter;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+
+import mslinks.data.*;
+
+public class LinkInfo implements Serializable {
+	private LinkInfoFlags lif;
+	private VolumeID vid;
+	private String localBasePath;
+	private CNRLink cnrlink;
+	private String commonPathSuffix;
+	
+	public LinkInfo() {
+		createVolumeID();
+	}
+	
+	public LinkInfo(ByteReader data) throws IOException, ShellLinkException {
+		int pos = data.getPosition();
+		int size = (int)data.read4bytes();
+		int hsize = (int)data.read4bytes();
+		lif = new LinkInfoFlags(data);
+		int vidoffset = (int)data.read4bytes();
+		int lbpoffset = (int)data.read4bytes();
+		int cnrloffset = (int)data.read4bytes();
+		int cpsoffset = (int)data.read4bytes();
+		int lbpoffset_u = 0, cpfoffset_u = 0;
+		if (hsize >= 0x24) {
+			lbpoffset_u = (int)data.read4bytes();
+			cpfoffset_u = (int)data.read4bytes();
+		}
+		
+		if (lif.hasVolumeIDAndLocalBasePath()) {
+			data.seek(pos + vidoffset - data.getPosition());
+			vid = new VolumeID(data);
+			data.seek(pos + lbpoffset - data.getPosition());
+			localBasePath = data.readString(pos, size);
+		}
+		if (lif.hasCommonNetworkRelativeLinkAndPathSuffix()) {
+			data.seek(pos + cnrloffset - data.getPosition());
+			cnrlink = new CNRLink(data);
+			data.seek(pos + cpsoffset - data.getPosition());
+			commonPathSuffix = data.readString(pos, size);
+		}
+		if (lif.hasVolumeIDAndLocalBasePath() && lbpoffset_u != 0) {
+			data.seek(pos + lbpoffset_u - data.getPosition());
+			localBasePath = data.readUnicodeString(pos, size);
+		}
+		if (lif.hasCommonNetworkRelativeLinkAndPathSuffix() && cpfoffset_u != 0) {
+			data.seek(pos + cpfoffset_u - data.getPosition());
+			commonPathSuffix = data.readUnicodeString(pos, size);
+		}
+		
+		data.seek(pos + size - data.getPosition());
+	}
+
+	public void serialize(ByteWriter bw) throws IOException {
+		int pos = bw.getPosition();
+		int hsize = 28;
+		CharsetEncoder ce = Charset.defaultCharset().newEncoder();
+		if (localBasePath != null && !ce.canEncode(localBasePath) || commonPathSuffix != null && !ce.canEncode(commonPathSuffix)) 
+			hsize += 8;
+		
+		byte[] vid_b = null, localBasePath_b = null, cnrlink_b = null, commonPathSuffix_b = null;
+		if (lif.hasVolumeIDAndLocalBasePath()) {
+			vid_b = toByteArray(vid, bw.isLitteEndian());
+			localBasePath_b = localBasePath.getBytes();
+			commonPathSuffix_b = new byte[0];
+		}
+		if (lif.hasCommonNetworkRelativeLinkAndPathSuffix()) {
+			cnrlink_b = toByteArray(cnrlink, bw.isLitteEndian());
+			commonPathSuffix_b = commonPathSuffix.getBytes();
+		}
+		
+		int size = hsize
+				+ (vid_b == null? 0 : vid_b.length)
+				+ (localBasePath_b == null? 0 : localBasePath_b.length + 1)
+				+ (cnrlink_b == null? 0 : cnrlink_b.length)
+				+ commonPathSuffix_b.length + 1;
+		
+		if (hsize > 28) {
+			if (lif.hasVolumeIDAndLocalBasePath()) {
+				size += localBasePath.length() * 2 + 2;
+				size += 1;
+			}
+			if (lif.hasCommonNetworkRelativeLinkAndPathSuffix()) {
+				size += commonPathSuffix.length() * 2 + 2;
+			} else 
+				size += 2;
+		}
+		
+		
+		bw.write4bytes(size);
+		bw.write4bytes(hsize);
+		lif.serialize(bw);
+		int off = hsize;
+		if (lif.hasVolumeIDAndLocalBasePath()) {
+			bw.write4bytes(off); // volumeid offset
+			off += vid_b.length;
+			bw.write4bytes(off); // localBasePath offset
+			off += localBasePath_b.length + 1;
+			bw.write4bytes(0); // CommonNetworkRelativeLinkOffset
+			bw.write4bytes(size - (hsize > 28 ? 4 : 1)); // fake commonPathSuffix offset 
+		}
+		if (lif.hasCommonNetworkRelativeLinkAndPathSuffix()) {
+			bw.write4bytes(0); // volumeid offset
+			bw.write4bytes(0); // localBasePath offset
+			bw.write4bytes(off); // CommonNetworkRelativeLink offset 
+			off += cnrlink_b.length;
+			bw.write4bytes(off); // commonPathSuffix
+			off += commonPathSuffix_b.length + 1;
+		}
+		if (hsize > 28) {
+			if (lif.hasVolumeIDAndLocalBasePath()) {
+				bw.write4bytes(off); // LocalBasePathOffsetUnicode
+				off += localBasePath.length() * 2 + 2;
+				bw.write4bytes(size - 2); // fake CommonPathSuffixUnicode offset
+			} else  {
+				bw.write4bytes(0);
+				bw.write4bytes(off); // CommonPathSuffixUnicode offset 
+				off += commonPathSuffix.length() * 2 + 2;
+			}				
+		}
+		
+		if (lif.hasVolumeIDAndLocalBasePath()) {
+			bw.writeBytes(vid_b);
+			bw.writeBytes(localBasePath_b);
+			bw.write(0);
+		}
+		if (lif.hasCommonNetworkRelativeLinkAndPathSuffix()) {
+			bw.writeBytes(cnrlink_b);
+			bw.writeBytes(commonPathSuffix_b);
+			bw.write(0);
+		}
+		
+		if (hsize > 28) {
+			if (lif.hasVolumeIDAndLocalBasePath()) {
+				for (int i=0; i<localBasePath.length(); i++)
+					bw.write2bytes(localBasePath.charAt(i));
+				bw.write2bytes(0);
+			}
+			if (lif.hasCommonNetworkRelativeLinkAndPathSuffix()) {
+				for (int i=0; i<commonPathSuffix.length(); i++)
+					bw.write2bytes(commonPathSuffix.charAt(i));
+				bw.write2bytes(0);
+			}
+		}
+		
+		while (bw.getPosition() < pos + size)
+			bw.write(0);		
+	}
+	
+	private byte[] toByteArray(Serializable o, boolean le) throws IOException {
+		ByteArrayOutputStream arr = new ByteArrayOutputStream();
+		ByteWriter bt = new ByteWriter(arr);
+		if (le) bt.setLittleEndian();
+		else bt.setBigEndian();
+		o.serialize(bt);
+		return arr.toByteArray();
+	}
+	
+	public VolumeID getVolumeID() { return vid; }
+	/**
+	 * Creates VolumeID and LocalBasePath that is empty string, clears CommonNetworkRelativeLink and CommonPathSuffix
+	 */
+	public VolumeID createVolumeID() {
+		cnrlink = null;
+		commonPathSuffix = null;
+		lif.clearCommonNetworkRelativeLinkAndPathSuffix();
+		
+		vid = new VolumeID();
+		localBasePath = "";
+		lif.setVolumeIDAndLocalBasePath();
+		return vid;
+	}
+	
+	public String getLocalBasePath() { return localBasePath; }
+	/**
+	 * Set LocalBasePath and creates new VolumeID (if it not exists), clears CommonNetworkRelativeLink and CommonPathSuffix.
+	 * If s is null takes no effect 
+	 */
+	public void setLocalBasePath(String s) {
+		if (s == null) return;
+		
+		localBasePath = s;
+		if (vid == null) vid = new VolumeID();
+		lif.setVolumeIDAndLocalBasePath();
+		
+		cnrlink = null;
+		commonPathSuffix = null;
+		lif.clearCommonNetworkRelativeLinkAndPathSuffix();
+	}
+	
+	public CNRLink getCommonNetworkRelativeLink() { return cnrlink; }
+	/**
+	 * Creates CommonNetworkRelativeLink and CommonPathSuffix that is empty string, clears VolumeID and LocalBasePath
+	 */
+	public CNRLink createCommonNetworkRelativeLink() {
+		cnrlink = new CNRLink();
+		commonPathSuffix = "";
+		lif.setCommonNetworkRelativeLinkAndPathSuffix();
+		
+		vid = null;
+		localBasePath = null;
+		lif.clearVolumeIDAndLocalBasePath();
+		
+		return cnrlink;
+	}
+	
+	public String getCommonPathSuffix() { return commonPathSuffix; }
+	/**
+	 * Set CommonPathSuffix and creates new CommonNetworkRelativeLink (if it not exists), clears VolumeID and LocalBasePath.
+	 * If s is null takes no effect 
+	 */
+	public void setCommonPathSuffix(String s) {
+		if (s == null) return;
+		
+		localBasePath = null;
+		vid = null;
+		lif.clearVolumeIDAndLocalBasePath();
+		
+		commonPathSuffix = s;
+		if (cnrlink == null) cnrlink = new CNRLink();		
+		lif.setCommonNetworkRelativeLinkAndPathSuffix();
+	}
+}
diff --git a/src/mslinks/LinkTargetIDList.java b/src/mslinks/LinkTargetIDList.java
index 4bd9f48f5db90743f7326b8e3c78415734ba98b1..4dcd2ac8ff1c901f9614048a3b70277c6de4fdde 100644
--- a/src/mslinks/LinkTargetIDList.java
+++ b/src/mslinks/LinkTargetIDList.java
@@ -6,13 +6,13 @@ import io.ByteWriter;
 import java.io.IOException;
 import java.util.LinkedList;
 
-public class LinkTargetIDList {
+public class LinkTargetIDList implements Serializable {
 	private LinkedList<byte[]> list = new LinkedList<>();
 	
 	public LinkTargetIDList(ByteReader data) throws IOException, ShellLinkException {
 		int size = (int)data.read2bytes();
 		
-		int check = data.getPosition(); 
+		int pos = data.getPosition(); 
 		
 		int s = (int)data.read2bytes();
 		while (s != 0) {
@@ -24,8 +24,8 @@ public class LinkTargetIDList {
 			s = (int)data.read2bytes();
 		}
 		
-		check = data.getPosition() - check;
-		if (check != size) 
+		pos = data.getPosition() - pos;
+		if (pos != size) 
 			throw new ShellLinkException();
 	}
 
diff --git a/src/mslinks/Main.java b/src/mslinks/Main.java
index 4395b58a4223984cf92aeefcc84450414e9fc335..1fed2fda23152a936da05fda4d5ad55e654e3cc1 100644
--- a/src/mslinks/Main.java
+++ b/src/mslinks/Main.java
@@ -9,8 +9,12 @@ import mslinks.data.Filetime;
 
 public class Main {
 	public static void main(String[] args) throws IOException, ShellLinkException {
-		ShellLink link = new ShellLink("testlink.lnk");
-		Filetime ft = link.getWriteTime();
+		//for (String i : Charset.availableCharsets().keySet())
+		//	System.out.println(i);
+		//if (true) return;
+		
+		ShellLink link = new ShellLink("testlink3.lnk");
+		Filetime ft = link.getHeader().getWriteTime();
 		System.out.println(String.format("%d:%d:%d %d.%d.%d", ft.get(GregorianCalendar.HOUR_OF_DAY), ft.get(GregorianCalendar.MINUTE), ft.get(GregorianCalendar.SECOND),
 				ft.get(GregorianCalendar.DAY_OF_MONTH), ft.get(GregorianCalendar.MONTH) + 1, ft.get(GregorianCalendar.YEAR)));
 		
diff --git a/src/mslinks/Serializable.java b/src/mslinks/Serializable.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec68dd58561d099df8f56884a0ff4da6d7fe10c9
--- /dev/null
+++ b/src/mslinks/Serializable.java
@@ -0,0 +1,9 @@
+package mslinks;
+
+import java.io.IOException;
+
+import io.ByteWriter;
+
+public interface Serializable {
+	void serialize(ByteWriter bw) throws IOException;
+}
diff --git a/src/mslinks/ShellLink.java b/src/mslinks/ShellLink.java
index d720fe49cc3ff95e134e6f0355a39ae27ee95911..87e71ce4b0ce4de86cb6361e2fe8a1a7e3e77a07 100644
--- a/src/mslinks/ShellLink.java
+++ b/src/mslinks/ShellLink.java
@@ -8,16 +8,12 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
-import mslinks.data.FileAttributesFlags;
-import mslinks.data.Filetime;
-import mslinks.data.HotKeyFlags;
-import mslinks.data.LinkFlags;
-
 public class ShellLink {
 	
 	private boolean le;
 	private ShellLinkHeader header;
 	private LinkTargetIDList idlist;
+	private LinkInfo info;
 	
 	public ShellLink(String file) throws IOException, ShellLinkException {
 		this(Paths.get(file));
@@ -39,6 +35,8 @@ public class ShellLink {
 		header = new ShellLinkHeader(data);
 		if (header.getLinkFlags().hasLinkTargetIDList()) 
 			idlist = new LinkTargetIDList(data);
+		if (header.getLinkFlags().hasLinkInfo())
+			info = new LinkInfo(data);
 		le = data.isLitteEndian();
 	}
 		
@@ -47,23 +45,17 @@ public class ShellLink {
 		if (le) bw.setLittleEndian();
 		else bw.setBigEndian();
 		header.serialize(bw);
-		idlist.serialize(bw);
+		if (header.getLinkFlags().hasLinkTargetIDList())
+			idlist.serialize(bw);
+		if (header.getLinkFlags().hasLinkInfo())
+			info.serialize(bw);
 	}
 	
-	/*    to header      */
-	public LinkFlags getLinkFlags() { return header.getLinkFlags(); }
-	public FileAttributesFlags getFileAttributesFlags() { return header.getFileAttributesFlags(); }
-	public Filetime getCreationTime() { return header.getCreationTime(); }
-	public Filetime getAccessTime() { return header.getAccessTime(); }
-	public Filetime getWriteTime() { return header.getWriteTime(); }
-	public HotKeyFlags getHotKeyFlags() { return header.getHotKeyFlags(); }
-	
-	public int getFileSize() { return header.getFileSize(); }
-	public void setFileSize(long n) { header.setFileSize(n); }
+	public ShellLinkHeader getHeader() { return header; }
 	
-	public int getIconIndex() { return header.getIconIndex(); }
-	public void setIconIndex(int n) { header.setIconIndex(n); }
-	
-	public int getShowCommand() { return header.getShowCommand(); }
-	public void setShowCommand(int n) throws ShellLinkException { header.setShowCommand(n); }
+	public LinkInfo getLinkInfo() { return info; }
+	public void createLinkInfo() {
+		info = new LinkInfo();
+		header.getLinkFlags().setHasLinkInfo();
+	}
 }
diff --git a/src/mslinks/ShellLinkHeader.java b/src/mslinks/ShellLinkHeader.java
index 673df7f6edf9898260fedf817d54af0815b0e4f4..2655ad38c92b4588313c5f73f9eaecb45fa857ff 100644
--- a/src/mslinks/ShellLinkHeader.java
+++ b/src/mslinks/ShellLinkHeader.java
@@ -12,7 +12,7 @@ import mslinks.data.GUID;
 import mslinks.data.HotKeyFlags;
 import mslinks.data.LinkFlags;
 
-public class ShellLinkHeader {
+public class ShellLinkHeader implements Serializable {
 	private static byte b(int i) { return (byte)i; }
 	private static int headerSize = 0x0000004C;
 	private static GUID clsid = new GUID(new byte[] {
@@ -22,9 +22,9 @@ public class ShellLinkHeader {
 			b(0xc0), b(0x00),  
 			b(0x00),  b(0x00),  b(0x00),  b(0x00),  b(0x00),  b(0x46) });
 	
-	public static int SW_SHOWNORMAL = 1;
-	public static int SW_SHOWMAXIMIZED = 3;
-	public static int SW_SHOWMINNOACTIVE = 7;	
+	public static final int SW_SHOWNORMAL = 1;
+	public static final int SW_SHOWMAXIMIZED = 3;
+	public static final int SW_SHOWMINNOACTIVE = 7;	
 	
 	private LinkFlags lf;
 	private FileAttributesFlags faf;
@@ -54,6 +54,8 @@ public class ShellLinkHeader {
 		fileSize = (int)data.read4bytes();
 		iconIndex = (int)data.read4bytes();
 		showCommand = (int)data.read4bytes();
+		if (showCommand != SW_SHOWNORMAL && showCommand != SW_SHOWMAXIMIZED && showCommand != SW_SHOWMINNOACTIVE)
+			throw new ShellLinkException();
 		hkf = new HotKeyFlags(data);
 		data.read2bytes();
 		data.read8bytes();
diff --git a/src/mslinks/data/BitSet32.java b/src/mslinks/data/BitSet32.java
index ae7bbc919eb2704565bbbdfaccb6efd6c35d6fae..89ae3b22e7829d7797a439202c28cc7aa03c4dbf 100644
--- a/src/mslinks/data/BitSet32.java
+++ b/src/mslinks/data/BitSet32.java
@@ -5,7 +5,9 @@ import io.ByteWriter;
 
 import java.io.IOException;
 
-public class BitSet32 {
+import mslinks.Serializable;
+
+public class BitSet32 implements Serializable {
 	private int d;
 	
 	public BitSet32(int n) {
diff --git a/src/mslinks/data/CNRLink.java b/src/mslinks/data/CNRLink.java
new file mode 100644
index 0000000000000000000000000000000000000000..546e99195ec2b678b593b4e02c97c4559f1bcd14
--- /dev/null
+++ b/src/mslinks/data/CNRLink.java
@@ -0,0 +1,201 @@
+package mslinks.data;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+
+import mslinks.Serializable;
+import mslinks.ShellLinkException;
+import io.ByteReader;
+import io.ByteWriter;
+
+public class CNRLink implements Serializable {
+	
+	public static final int WNNC_NET_AVID = 0x001A000;
+	public static final int WNNC_NET_DOCUSPACE = 0x001B000;
+	public static final int WNNC_NET_MANGOSOFT = 0x001C000;
+	public static final int WNNC_NET_SERNET = 0x001D000;
+	public static final int WNNC_NET_RIVERFRONT1 = 0X001E000;
+	public static final int WNNC_NET_RIVERFRONT2 = 0x001F000;
+	public static final int WNNC_NET_DECORB = 0x0020000;
+	public static final int WNNC_NET_PROTSTOR = 0x0021000;
+	public static final int WNNC_NET_FJ_REDIR = 0x0022000;
+	public static final int WNNC_NET_DISTINCT = 0x0023000;
+	public static final int WNNC_NET_TWINS = 0x0024000;
+	public static final int WNNC_NET_RDR2SAMPLE = 0x0025000;
+	public static final int WNNC_NET_CSC = 0x0026000;
+	public static final int WNNC_NET_3IN1 = 0x0027000;
+	public static final int WNNC_NET_EXTENDNET = 0x0029000;
+	public static final int WNNC_NET_STAC = 0x002A000;
+	public static final int WNNC_NET_FOXBAT = 0x002B000;
+	public static final int WNNC_NET_YAHOO = 0x002C000;
+	public static final int WNNC_NET_EXIFS = 0x002D000;
+	public static final int WNNC_NET_DAV = 0x002E000;
+	public static final int WNNC_NET_KNOWARE = 0x002F000;
+	public static final int WNNC_NET_OBJECT_DIRE = 0x0030000;
+	public static final int WNNC_NET_MASFAX = 0x0031000;
+	public static final int WNNC_NET_HOB_NFS = 0x0032000;
+	public static final int WNNC_NET_SHIVA = 0x0033000;
+	public static final int WNNC_NET_IBMAL = 0x0034000;
+	public static final int WNNC_NET_LOCK = 0x0035000;
+	public static final int WNNC_NET_TERMSRV = 0x0036000;
+	public static final int WNNC_NET_SRT = 0x0037000;
+	public static final int WNNC_NET_QUINCY = 0x0038000;
+	public static final int WNNC_NET_OPENAFS = 0x0039000;
+	public static final int WNNC_NET_AVID1 = 0X003A000;
+	public static final int WNNC_NET_DFS = 0x003B000;
+	public static final int WNNC_NET_KWNP = 0x003C000;
+	public static final int WNNC_NET_ZENWORKS = 0x003D000;
+	public static final int WNNC_NET_DRIVEONWEB = 0x003E000;
+	public static final int WNNC_NET_VMWARE = 0x003F000;
+	public static final int WNNC_NET_RSFX = 0x0040000;
+	public static final int WNNC_NET_MFILES = 0x0041000;
+	public static final int WNNC_NET_MS_NFS = 0x0042000;
+	public static final int WNNC_NET_GOOGLE = 0x0043000;
+	
+	private CNRLinkFlags flags; 
+	private int nptype;
+	private String netname, devname;
+
+	public CNRLink() {
+		netname = "";
+	}
+	
+	public CNRLink(ByteReader data) throws ShellLinkException, IOException {
+		int pos = data.getPosition();
+		int size = (int)data.read4bytes();
+		if (size < 0x14)
+			throw new ShellLinkException();
+		flags = new CNRLinkFlags(data);
+		int nnoffset = (int)data.read4bytes();
+		int dnoffset = (int)data.read4bytes();
+		if (!flags.isValidDevice())
+			dnoffset = 0;
+		nptype = (int)data.read4bytes();
+		if (flags.isValidNetType())
+			checkNptype(nptype);
+		else nptype = 0;
+		
+		int nnoffset_u = 0, dnoffset_u = 0;
+		if (nnoffset > 0x14) {
+			nnoffset_u = (int)data.read4bytes();
+			dnoffset_u = (int)data.read4bytes();
+		}
+		
+		data.seek(pos + nnoffset - data.getPosition());
+		netname = data.readString( pos, size);
+		if (dnoffset != 0) {
+			data.seek(pos + dnoffset - data.getPosition());
+			devname = data.readString(pos, size);
+		}
+		if (nnoffset_u != 0) netname = data.readUnicodeString(pos, size);
+		if (dnoffset_u != 0) devname = data.readUnicodeString(pos, size);
+	}
+	
+	private void checkNptype(int type) throws ShellLinkException {
+		int mod = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL;
+		for (Field f : this.getClass().getFields()) {
+			try {
+				if ((f.getModifiers() & mod) == mod && type == ((Integer)f.get(null)).intValue())
+					return;
+			} catch (Exception e) {}
+		}
+		throw new ShellLinkException("incorrect network type");
+	}
+
+	@Override
+	public void serialize(ByteWriter bw) throws IOException {
+		int size = 20;
+		boolean u = false;
+		CharsetEncoder ce = Charset.defaultCharset().newEncoder();
+		u = !ce.canEncode(netname) || devname != null && !ce.canEncode(devname);
+		
+		if (u) size += 8;
+		byte[] netname_b = null, devname_b = null;
+		netname_b = netname.getBytes();
+		if (devname != null) devname_b = devname.getBytes();
+		size += netname_b.length + 1;
+		if (devname_b != null) size += devname_b.length + 1;
+		
+		if (u) {
+			size += netname.length() * 2 + 2;
+			if (devname != null) size += devname.length() * 2 + 2;
+		}
+		
+		bw.write4bytes(size);
+		flags.serialize(bw);
+		int off = 20;
+		if (u) off += 8;
+		bw.write4bytes(off); // netname offset
+		off += netname_b.length + 1;
+		if (devname_b != null) {
+			bw.write4bytes(off); // devname offset
+			off += devname_b.length + 1;
+		} else bw.write4bytes(0);
+		bw.write4bytes(nptype);
+		if (u) {
+			bw.write4bytes(off);
+			off += netname.length() * 2 + 2;
+			if (devname != null) {
+				bw.write4bytes(off);
+				off += devname.length() * 2 + 2;
+			} else bw.write4bytes(0);
+		}
+		bw.writeBytes(netname_b);
+		bw.write(0);
+		if (devname_b != null) {
+			bw.writeBytes(devname_b);
+			bw.write(0);
+		}
+		if (u) {
+			for (int i=0; i<netname.length(); i++)
+				bw.write2bytes(netname.charAt(i));
+			bw.write2bytes(0);
+			if (devname != null) {
+				for (int i=0; i<devname.length(); i++)
+					bw.write2bytes(devname.charAt(i));
+				bw.write2bytes(0);
+			}
+		}
+	}
+	
+	public int getNetworkType() { return nptype; }
+	/**
+	 * pass zero to switch off network type
+	 */
+	public void setNetworlType(int n) throws ShellLinkException {
+		if (n == 0) {
+			flags.clearValidNetType();
+			nptype = n;
+		} else {
+			checkNptype(n);
+			flags.setValidNetType();
+			nptype = n;
+		}
+	}
+	
+	public String getNetName() { return netname; }
+	/** 
+	 * if s is null take no effect
+	 */
+	public void setNetName(String s) throws ShellLinkException {
+		if (s == null) return;
+		netname = s; 
+	}
+	
+	public String getDeviceName() { return devname; }
+	/**
+	 * pass null to switch off device info
+	 */
+	public void setDeviceName(String s) {
+		if (s == null) {
+			devname = null;
+			flags.clearValidDevice();
+		} else {
+			devname = s;
+			flags.setValidDevice();
+		}
+	}
+}
diff --git a/src/mslinks/data/CNRLinkFlags.java b/src/mslinks/data/CNRLinkFlags.java
new file mode 100644
index 0000000000000000000000000000000000000000..69438d29426cbd9bfd278d79e0f0aee5a9d0754d
--- /dev/null
+++ b/src/mslinks/data/CNRLinkFlags.java
@@ -0,0 +1,33 @@
+package mslinks.data;
+
+import io.ByteReader;
+
+import java.io.IOException;
+
+public class CNRLinkFlags extends BitSet32 {
+	
+	public CNRLinkFlags(int n) {
+		super(n);
+		reset();
+	}
+
+	public CNRLinkFlags(ByteReader data) throws IOException {
+		super(data);
+		reset();
+	}
+	
+	private void reset() {
+		for (int i=2; i<32; i++)
+			clear(i);
+	}
+	
+	public boolean isValidDevice() 		{ return get(0); }
+	public boolean isValidNetType()		{ return get(1); }
+	
+	public void setValidDevice() 		{ set(0); }	
+	public void setValidNetType()		{ set(1); }
+	
+	public void clearValidDevice() 		{ clear(0); }	
+	public void clearValidNetType()		{ clear(1); }
+
+}
diff --git a/src/mslinks/data/FileAttributesFlags.java b/src/mslinks/data/FileAttributesFlags.java
index d8fb16660392a19533537180b2a5cb08d3b186b5..533c13966105e113f508cd7b26e7e909b83d6762 100644
--- a/src/mslinks/data/FileAttributesFlags.java
+++ b/src/mslinks/data/FileAttributesFlags.java
@@ -7,10 +7,19 @@ import java.io.IOException;
 public class FileAttributesFlags extends BitSet32 {
 	public FileAttributesFlags(int n) {
 		super(n);
+		reset();
 	}
 	
 	public FileAttributesFlags(ByteReader data) throws IOException {
 		super(data);
+		reset();
+	}
+	
+	private void reset() {
+		clear(3);
+		clear(6);
+		for (int i=15; i<32; i++)
+			clear(i);
 	}
 	
 	public boolean isReadonly() 			{ return get(0); }
diff --git a/src/mslinks/data/Filetime.java b/src/mslinks/data/Filetime.java
index 3fc824f3291cd22ff145befd800feb865214e191..2d216363ddaf908ee86fbea0875befac097580da 100644
--- a/src/mslinks/data/Filetime.java
+++ b/src/mslinks/data/Filetime.java
@@ -6,7 +6,9 @@ import io.ByteWriter;
 import java.io.IOException;
 import java.util.GregorianCalendar;
 
-public class Filetime extends GregorianCalendar {
+import mslinks.Serializable;
+
+public class Filetime extends GregorianCalendar implements Serializable {
 	private long residue;
 	
 	public Filetime(ByteReader data) throws IOException {
diff --git a/src/mslinks/data/GUID.java b/src/mslinks/data/GUID.java
index 4c820a0f8e043dac42df0c49bf6f93fabd4c0dc4..c3e65a30e04352c232a393445401670d8615f195 100644
--- a/src/mslinks/data/GUID.java
+++ b/src/mslinks/data/GUID.java
@@ -6,7 +6,9 @@ import io.Bytes;
 
 import java.io.IOException;
 
-public class GUID {
+import mslinks.Serializable;
+
+public class GUID implements Serializable {
 	private int d1;
 	private short d2, d3, d4;
 	private long d5;
diff --git a/src/mslinks/data/HotKeyFlags.java b/src/mslinks/data/HotKeyFlags.java
index 6c780aac3d59cd6791866141b9d023f9e5b9b2d8..c919094222793c704c07a68c733c02dd9909ceba 100644
--- a/src/mslinks/data/HotKeyFlags.java
+++ b/src/mslinks/data/HotKeyFlags.java
@@ -6,7 +6,9 @@ import io.ByteWriter;
 import java.io.IOException;
 import java.util.HashMap;
 
-public class HotKeyFlags {
+import mslinks.Serializable;
+
+public class HotKeyFlags implements Serializable {
 	private static HashMap<Byte, String> keys = new HashMap<Byte, String>() {{
 		put((byte)0x30, "0");
 		put((byte)0x31, "1");
diff --git a/src/mslinks/data/LinkFlags.java b/src/mslinks/data/LinkFlags.java
index c565cbd4796945249c95d625888f0a45007faffd..fd65096d54b2e1d455e88ec8dfa669c7a66650ef 100644
--- a/src/mslinks/data/LinkFlags.java
+++ b/src/mslinks/data/LinkFlags.java
@@ -8,10 +8,19 @@ public class LinkFlags extends BitSet32 {
 	
 	public LinkFlags(int n) {
 		super(n);
+		reset();
 	}
 	
 	public LinkFlags(ByteReader data) throws IOException {
 		super(data);
+		reset();
+	}
+	
+	private void reset() {
+		clear(11);
+		clear(16);
+		for (int i=27; i<32; i++)
+			clear(i);
 	}
 	
 	public boolean hasLinkTargetIDList() 			{ return get(0); }	
diff --git a/src/mslinks/data/LinkInfoFlags.java b/src/mslinks/data/LinkInfoFlags.java
new file mode 100644
index 0000000000000000000000000000000000000000..3514c68819db760e5dca405ed159ca9925db0159
--- /dev/null
+++ b/src/mslinks/data/LinkInfoFlags.java
@@ -0,0 +1,32 @@
+package mslinks.data;
+
+import io.ByteReader;
+
+import java.io.IOException;
+
+public class LinkInfoFlags extends BitSet32 {
+	
+	public LinkInfoFlags(int n) {
+		super(n);
+		reset();
+	}
+	
+	public LinkInfoFlags(ByteReader data) throws IOException {
+		super(data);
+		reset();
+	}
+	
+	private void reset() {
+		for (int i=2; i<32; i++)
+			clear(i);
+	}
+	
+	public boolean hasVolumeIDAndLocalBasePath() 				{ return get(0); }
+	public boolean hasCommonNetworkRelativeLinkAndPathSuffix()	{ return get(1); }
+	
+	public void setVolumeIDAndLocalBasePath() 					{ set(0); }	
+	public void setCommonNetworkRelativeLinkAndPathSuffix()		{ set(1); }
+	
+	public void clearVolumeIDAndLocalBasePath() 				{ clear(0); }	
+	public void clearCommonNetworkRelativeLinkAndPathSuffix()	{ clear(1); }
+}
diff --git a/src/mslinks/data/VolumeID.java b/src/mslinks/data/VolumeID.java
new file mode 100644
index 0000000000000000000000000000000000000000..f186d78024f5a2d2f914601aebe15900ad501d19
--- /dev/null
+++ b/src/mslinks/data/VolumeID.java
@@ -0,0 +1,125 @@
+package mslinks.data;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import mslinks.Serializable;
+import mslinks.ShellLinkException;
+import io.ByteReader;
+import io.ByteWriter;
+
+public class VolumeID implements Serializable {
+	public static final int DRIVE_UNKNOWN = 0;
+	public static final int DRIVE_NO_ROOT_DIR = 1;
+	public static final int DRIVE_REMOVABLE = 2;
+	public static final int DRIVE_FIXED = 3;
+	public static final int DRIVE_REMOTE = 4;
+	public static final int DRIVE_CDROM = 5;
+	public static final int DRIVE_RAMDISK = 6;
+	
+	
+	private int dt;
+	private int dsn;
+	private String label;
+
+	public VolumeID() {
+		dt = DRIVE_UNKNOWN;
+		dsn = (int)(Math.random() * Long.MAX_VALUE);
+		label = "";
+	}
+	
+	public VolumeID(ByteReader data) throws ShellLinkException, IOException {
+		int pos = data.getPosition();
+		int size = (int)data.read4bytes();
+		if (size <= 0x10)
+			throw new ShellLinkException();
+		
+		dt = (int)data.read4bytes();
+		if (dt != DRIVE_NO_ROOT_DIR && dt != DRIVE_REMOVABLE && dt != DRIVE_FIXED 
+				&& dt != DRIVE_REMOTE && dt != DRIVE_CDROM && dt != DRIVE_RAMDISK)
+			dt = DRIVE_UNKNOWN;
+		dsn = (int)data.read4bytes();
+		int vloffset = (int)data.read4bytes();
+		boolean u = false;
+		if (vloffset == 0x14) {
+			vloffset = (int)data.read4bytes();
+			u = true;
+		}
+
+		data.seek(pos + vloffset - data.getPosition());
+		
+		int i=0;
+		if (u) {
+			char[] buf = new char[(size-vloffset)>>1];
+			for (;; i++) {
+				char c = (char)data.read2bytes();
+				if (c == 0) break;
+				buf[i] = c;
+			}
+			label = new String(buf, 0, i);
+		} else {
+			byte[] buf = new byte[size-vloffset];
+			for (;; i++) {
+				int b = data.read();
+				if (b == 0) break;
+				buf[i] = (byte)b;
+			}
+			label = new String(buf, 0, i);
+		}
+	}
+	
+	public void serialize(ByteWriter bw) throws IOException {
+		int size = 16;
+		byte[] label_b = label.getBytes();
+		size += label_b.length + 1;
+		boolean u = false;
+		if (!Charset.defaultCharset().newEncoder().canEncode(label)) { 
+			size += 4 + 1 + label.length() * 2 + 2;
+			u = true;
+		}
+		
+		bw.write4bytes(size);
+		bw.write4bytes(dt);
+		bw.write4bytes(dsn);
+		int off = 16;
+		if (u) off += 4;
+		bw.write4bytes(off);
+		off += label_b.length + 1;		
+		if (u) {
+			off++;
+			bw.write4bytes(off);
+			off += label.length() * 2 + 2;
+		}
+		
+		bw.writeBytes(label_b);
+		bw.write(0);
+		if (u) {
+			bw.write(0);
+			for (int i=0; i<label.length(); i++)
+				bw.write2bytes(label.charAt(i));
+			bw.write2bytes(0);
+		}
+	}
+	
+	public int getDriveType() { return dt;}
+	public void setDriveType(int n) throws ShellLinkException {
+		if (n == DRIVE_UNKNOWN || n == DRIVE_NO_ROOT_DIR || n == DRIVE_REMOVABLE || n == DRIVE_FIXED 
+				|| n == DRIVE_REMOTE || n == DRIVE_CDROM || n == DRIVE_RAMDISK)
+			dt = n;
+		else 
+			throw new ShellLinkException("incorrect drive type");
+	}
+	
+	public int getSerialNumber() { return dsn; }
+	public void setSerialNumber(int n) { dsn = n; }
+	
+	public String getLabel() { return label; }
+	/** 
+	 * if s is null take no effect
+	 */
+	public void setLabel(String s) {
+		if (s == null) return;
+		label = s;
+	}
+
+}
diff --git a/test.lnk b/test.lnk
index 7dea24b522b7019a5816e696f1463e6a2c8821ab..d8688532def8dbb7b23430c8e5233522249c54fa 100644
Binary files a/test.lnk and b/test.lnk differ
diff --git a/testlink.lnk b/testlink.lnk
index 869c5c67a2db6a48a8d98ab8bb9fa422890fed37..edce87d281ba6ff62ee8225626bf5fdfe21a1661 100644
Binary files a/testlink.lnk and b/testlink.lnk differ
diff --git a/testlink2.lnk b/testlink2.lnk
new file mode 100644
index 0000000000000000000000000000000000000000..a28907e3c32664b53979dc7f2d65c3c925abf501
Binary files /dev/null and b/testlink2.lnk differ
diff --git a/testlink3.lnk b/testlink3.lnk
new file mode 100644
index 0000000000000000000000000000000000000000..35f78d76f470af829aec901d3b277c2ef0247e07
Binary files /dev/null and b/testlink3.lnk differ
diff --git a/testlink4.lnk b/testlink4.lnk
new file mode 100644
index 0000000000000000000000000000000000000000..84f15f7bc5b3c09d830897392de8088e4d84aa20
Binary files /dev/null and b/testlink4.lnk differ
diff --git a/testlink5.lnk b/testlink5.lnk
new file mode 100644
index 0000000000000000000000000000000000000000..d4866223362b1c9741c2b2a023d7482dc81cf4dd
Binary files /dev/null and b/testlink5.lnk differ
diff --git a/testlink6.lnk b/testlink6.lnk
new file mode 100644
index 0000000000000000000000000000000000000000..346797d658169a1fec2e5600cf9e800c07def958
Binary files /dev/null and b/testlink6.lnk differ