Advertisement
Chiddix

OnDemandRequester

Feb 3rd, 2013
151
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 18.05 KB | None | 0 0
  1. package com.runescape.cache.requester;
  2.  
  3. import java.io.ByteArrayInputStream;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.io.OutputStream;
  7. import java.net.Socket;
  8. import java.util.zip.CRC32;
  9. import java.util.zip.GZIPInputStream;
  10.  
  11. import com.runescape.Client;
  12. import com.runescape.cache.Archive;
  13. import com.runescape.net.Buffer;
  14. import com.runescape.node.LinkedList;
  15. import com.runescape.node.Queue;
  16. import com.runescape.util.SignLink;
  17.  
  18. public class OnDemandRequester extends Requester implements Runnable {
  19.  
  20.     private int extrasTotal;
  21.     private LinkedList sentRequests = new LinkedList();
  22.     private int highestPriority;
  23.     public String message = "";
  24.     private int sinceKeepAlive;
  25.     private long lastSocketOpen;
  26.     private int[] regLandIndex;
  27.     private CRC32 crc32 = new CRC32();
  28.     private byte[] inputBuffer = new byte[500];
  29.     public int cycle;
  30.     private byte[][] filePriorities = new byte[4][];
  31.     private Client client;
  32.     private LinkedList passiveRequests = new LinkedList();
  33.     private int offset;
  34.     private int toRead;
  35.     private int[] midiIndex;
  36.     public int requestFails;
  37.     private int[] regMapIndex;
  38.     private int extrasLoaded;
  39.     private boolean running = true;
  40.     private OutputStream outputStream;
  41.     private int[] regShouldPreload;
  42.     private boolean expectData = false;
  43.     private LinkedList completed = new LinkedList();
  44.     private byte[] deflateOut = new byte[65000];
  45.     private int[] animIndex;
  46.     private Queue immediateRequests = new Queue();
  47.     private InputStream inputStream;
  48.     private Socket socket;
  49.     private int[][] fileVersions = new int[4][];
  50.     private int[][] fileCrc = new int[4][];
  51.     private int immediateRequestsSent;
  52.     private int passiveRequestsSent;
  53.     private LinkedList toRequest = new LinkedList();
  54.     private OnDemandNode onDemandNode;
  55.     private LinkedList wanted = new LinkedList();
  56.     private int[] regHash;
  57.     private byte[] modelIndex;
  58.     private int idleCycles;
  59.  
  60.     private final boolean verify(int expectedVersion, int expectedCrc, byte[] data) {
  61.         if (data == null || data.length < 2) {
  62.             return false;
  63.         }
  64.         int length = data.length - 2;
  65.         int version = ((data[length] & 0xff) << 8) + (data[length + 1] & 0xff);
  66.         crc32.reset();
  67.         crc32.update(data, 0, length);
  68.         int crc = (int) crc32.getValue();
  69.         if (version != expectedVersion) {
  70.             return false;
  71.         }
  72.         if (crc != expectedCrc) {
  73.             return false;
  74.         }
  75.         return true;
  76.     }
  77.  
  78.     private final void handleResp() {
  79.         try {
  80.             int available = inputStream.available();
  81.             if (toRead == 0 && available >= 6) {
  82.                 expectData = true;
  83.                 for (int i = 0; i < 6; i += inputStream.read(inputBuffer, i, 6 - i)) {
  84.                     ;
  85.                 }
  86.                 int type = inputBuffer[0] & 0xff;
  87.                 int id = ((inputBuffer[1] & 0xff) << 8) + (inputBuffer[2] & 0xff);
  88.                 int size = ((inputBuffer[3] & 0xff) << 8) + (inputBuffer[4] & 0xff);
  89.                 int part = inputBuffer[5] & 0xff;
  90.                 onDemandNode = null;
  91.                 for (OnDemandNode ondemandnode = (OnDemandNode) sentRequests.getBack(); ondemandnode != null; ondemandnode = (OnDemandNode) sentRequests
  92.                         .getPrevious()) {
  93.                     if (ondemandnode.type == type && ondemandnode.id == id) {
  94.                         onDemandNode = ondemandnode;
  95.                     }
  96.                     if (onDemandNode != null) {
  97.                         ondemandnode.cyclesSinceSend = 0;
  98.                     }
  99.                 }
  100.                 if (onDemandNode != null) {
  101.                     idleCycles = 0;
  102.                     if (size == 0) {
  103.                         SignLink.reportError("Rej: " + type + "," + id);
  104.                         onDemandNode.buffer = null;
  105.                         if (onDemandNode.immediate) {
  106.                             synchronized (completed) {
  107.                                 completed.insertBack(onDemandNode);
  108.                             }
  109.                         } else {
  110.                             onDemandNode.remove();
  111.                         }
  112.                         onDemandNode = null;
  113.                     } else {
  114.                         if (onDemandNode.buffer == null && part == 0) {
  115.                             onDemandNode.buffer = new byte[size];
  116.                         }
  117.                         if (onDemandNode.buffer == null && part != 0) {
  118.                             throw new IOException("missing start of file");
  119.                         }
  120.                     }
  121.                 }
  122.                 offset = part * 500;
  123.                 toRead = 500;
  124.                 if (toRead > size - part * 500) {
  125.                     toRead = size - part * 500;
  126.                 }
  127.             }
  128.             if (toRead <= 0 || available < toRead) {
  129.                 return;
  130.             }
  131.             expectData = true;
  132.             byte[] buffer = inputBuffer;
  133.             int bufferOffset = 0;
  134.             if (onDemandNode != null) {
  135.                 buffer = onDemandNode.buffer;
  136.                 bufferOffset = offset;
  137.             }
  138.             for (int i = 0; i < toRead; i += inputStream.read(buffer, i + bufferOffset, toRead - i)) {
  139.                 ;
  140.             }
  141.             if (toRead + offset >= buffer.length && onDemandNode != null) {
  142.                 if (client.stores[0] != null) {
  143.                     client.stores[onDemandNode.type + 1].put(buffer.length, buffer, onDemandNode.id);
  144.                 }
  145.                 if (!onDemandNode.immediate && onDemandNode.type == 3) {
  146.                     onDemandNode.immediate = true;
  147.                     onDemandNode.type = 93;
  148.                 }
  149.                 if (onDemandNode.immediate) {
  150.                     synchronized (completed) {
  151.                         completed.insertBack(onDemandNode);
  152.                     }
  153.                 } else {
  154.                     onDemandNode.remove();
  155.                 }
  156.             }
  157.             toRead = 0;
  158.         } catch (IOException ioexception) {
  159.             try {
  160.                 socket.close();
  161.             } catch (Exception exception) {
  162.                 /* empty */
  163.             }
  164.             socket = null;
  165.             inputStream = null;
  166.             outputStream = null;
  167.             toRead = 0;
  168.         }
  169.     }
  170.  
  171.     public final void init(Archive archive, Client client) {
  172.         String[] versionFiles = { "model_version", "anim_version", "midi_version", "map_version" };
  173.         for (int version = 0; version < 4; version++) {
  174.             byte[] data = archive.getFile(versionFiles[version]);
  175.             int versionCount = data.length / 2;
  176.             Buffer buffer = new Buffer(data);
  177.             fileVersions[version] = new int[versionCount];
  178.             filePriorities[version] = new byte[versionCount];
  179.             for (int file = 0; file < versionCount; file++) {
  180.                 fileVersions[version][file] = buffer.getUnsignedLEShort();
  181.             }
  182.         }
  183.         String[] crcFiles = { "model_crc", "anim_crc", "midi_crc", "map_crc" };
  184.         for (int crc = 0; crc < 4; crc++) {
  185.             byte[] data = archive.getFile(crcFiles[crc]);
  186.             int crcCount = data.length / 4;
  187.             Buffer buffer = new Buffer(data);
  188.             fileCrc[crc] = new int[crcCount];
  189.             for (int file = 0; file < crcCount; file++) {
  190.                 fileCrc[crc][file] = buffer.getInt();
  191.             }
  192.         }
  193.         byte[] data = archive.getFile("model_index");
  194.         int count = fileVersions[0].length;
  195.         modelIndex = new byte[count];
  196.         for (int i = 0; i < count; i++) {
  197.             if (i < data.length) {
  198.                 modelIndex[i] = data[i];
  199.             } else {
  200.                 modelIndex[i] = (byte) 0;
  201.             }
  202.         }
  203.         data = archive.getFile("map_index");
  204.         Buffer buffer = new Buffer(data);
  205.         count = data.length / 7;
  206.         regHash = new int[count];
  207.         regMapIndex = new int[count];
  208.         regLandIndex = new int[count];
  209.         regShouldPreload = new int[count];
  210.         for (int reg = 0; reg < count; reg++) {
  211.             regHash[reg] = buffer.getUnsignedLEShort();
  212.             regMapIndex[reg] = buffer.getUnsignedLEShort();
  213.             regLandIndex[reg] = buffer.getUnsignedLEShort();
  214.             regShouldPreload[reg] = buffer.getUnsignedByte();
  215.         }
  216.         data = archive.getFile("anim_index");
  217.         buffer = new Buffer(data);
  218.         count = data.length / 2;
  219.         animIndex = new int[count];
  220.         for (int i = 0; i < count; i++) {
  221.             animIndex[i] = buffer.getUnsignedLEShort();
  222.         }
  223.         data = archive.getFile("midi_index");
  224.         buffer = new Buffer(data);
  225.         count = data.length;
  226.         midiIndex = new int[count];
  227.         for (int i = 0; i < count; i++) {
  228.             midiIndex[i] = buffer.getUnsignedByte();
  229.         }
  230.         this.client = client;
  231.         this.running = true;
  232.         this.client.startRunnable(this, 2);
  233.     }
  234.  
  235.     public final int immediateRequestCount() {
  236.         synchronized (immediateRequests) {
  237.             return immediateRequests.getNodeCount();
  238.         }
  239.     }
  240.  
  241.     public final void stop() {
  242.         running = false;
  243.     }
  244.  
  245.     public final void preloadRegions(boolean members) {
  246.         for (int reg = 0; reg < regHash.length; reg++) {
  247.             if (members || regShouldPreload[reg] != 0) {
  248.                 setPriority((byte) 2, 3, regLandIndex[reg]);
  249.                 setPriority((byte) 2, 3, regMapIndex[reg]);
  250.             }
  251.         }
  252.     }
  253.  
  254.     public final int fileCount(int file) {
  255.         return fileVersions[file].length;
  256.     }
  257.  
  258.     private final void sendRequest(OnDemandNode onDemandNode) {
  259.         try {
  260.             if (socket == null) {
  261.                 long currentTime = System.currentTimeMillis();
  262.                 if (currentTime - lastSocketOpen < 4000L) {
  263.                     return;
  264.                 }
  265.                 lastSocketOpen = currentTime;
  266.                 socket = client.createSocket(43594 + Client.portOffset);
  267.                 inputStream = socket.getInputStream();
  268.                 outputStream = socket.getOutputStream();
  269.                 outputStream.write(15);
  270.                 for (int i = 0; i < 8; i++) {
  271.                     inputStream.read();
  272.                 }
  273.                 idleCycles = 0;
  274.             }
  275.             inputBuffer[0] = (byte) onDemandNode.type;
  276.             inputBuffer[1] = (byte) (onDemandNode.id >> 8);
  277.             inputBuffer[2] = (byte) onDemandNode.id;
  278.             if (onDemandNode.immediate) {
  279.                 inputBuffer[3] = (byte) 2;
  280.             } else if (!client.loggedIn) {
  281.                 inputBuffer[3] = (byte) 1;
  282.             } else {
  283.                 inputBuffer[3] = (byte) 0;
  284.             }
  285.             outputStream.write(inputBuffer, 0, 4);
  286.             sinceKeepAlive = 0;
  287.             requestFails = -10000;
  288.         } catch (IOException ioexception) {
  289.             try {
  290.                 socket.close();
  291.             } catch (Exception exception) {
  292.             }
  293.             socket = null;
  294.             inputStream = null;
  295.             outputStream = null;
  296.             toRead = 0;
  297.             requestFails++;
  298.         }
  299.     }
  300.  
  301.     public final int animCount() {
  302.         return animIndex.length;
  303.     }
  304.  
  305.     public final void request(int type, int id) {
  306.         if (type >= 0 && type <= fileVersions.length && id >= 0 && id <= fileVersions[type].length
  307.                 && fileVersions[type][id] != 0) {
  308.             synchronized (immediateRequests) {
  309.                 for (OnDemandNode onDemandNode = (OnDemandNode) immediateRequests.reverseGetFirst(); onDemandNode != null; onDemandNode = (OnDemandNode) immediateRequests
  310.                         .reverseGetNext()) {
  311.                     if (onDemandNode.type == type && onDemandNode.id == id) {
  312.                         return;
  313.                     }
  314.                 }
  315.                 OnDemandNode ondemandnode = new OnDemandNode();
  316.                 ondemandnode.type = type;
  317.                 ondemandnode.id = id;
  318.                 ondemandnode.immediate = true;
  319.                 synchronized (wanted) {
  320.                     wanted.insertBack(ondemandnode);
  321.                 }
  322.                 immediateRequests.insertHead(ondemandnode);
  323.             }
  324.         }
  325.     }
  326.  
  327.     public final int modelIndex(int model) {
  328.         return modelIndex[model] & 0xff;
  329.     }
  330.  
  331.     @Override
  332.     public final void run() {
  333.         try {
  334.             while (running) {
  335.                 cycle++;
  336.                 int toWait = 20;
  337.                 if (highestPriority == 0 && client.stores[0] != null) {
  338.                     toWait = 50;
  339.                 }
  340.                 Thread.sleep(toWait);
  341.                 expectData = true;
  342.                 for (int i = 0; i < 100; i++) {
  343.                     if (!expectData) {
  344.                         break;
  345.                     }
  346.                     expectData = false;
  347.                     localComplete();
  348.                     remainingRequest();
  349.                     if (immediateRequestsSent == 0 && i >= 5) {
  350.                         break;
  351.                     }
  352.                     passivesRequest();
  353.                     if (inputStream != null) {
  354.                         handleResp();
  355.                     }
  356.                 }
  357.                 boolean idle = false;
  358.                 for (OnDemandNode onDemandNode = (OnDemandNode) sentRequests.getBack(); onDemandNode != null; onDemandNode = (OnDemandNode) sentRequests
  359.                         .getPrevious()) {
  360.                     if (onDemandNode.immediate) {
  361.                         idle = true;
  362.                         onDemandNode.cyclesSinceSend++;
  363.                         if (onDemandNode.cyclesSinceSend > 50) {
  364.                             onDemandNode.cyclesSinceSend = 0;
  365.                             sendRequest(onDemandNode);
  366.                         }
  367.                     }
  368.                 }
  369.                 if (!idle) {
  370.                     for (OnDemandNode onDemandNode = (OnDemandNode) sentRequests.getBack(); onDemandNode != null; onDemandNode = (OnDemandNode) sentRequests
  371.                             .getPrevious()) {
  372.                         idle = true;
  373.                         onDemandNode.cyclesSinceSend++;
  374.                         if (onDemandNode.cyclesSinceSend > 50) {
  375.                             onDemandNode.cyclesSinceSend = 0;
  376.                             sendRequest(onDemandNode);
  377.                         }
  378.                     }
  379.                 }
  380.                 if (idle) {
  381.                     idleCycles++;
  382.                     if (idleCycles > 750) {
  383.                         socket.close();
  384.                         socket = null;
  385.                         inputStream = null;
  386.                         outputStream = null;
  387.                         toRead = 0;
  388.                     }
  389.                 } else {
  390.                     idleCycles = 0;
  391.                     message = "";
  392.                 }
  393.                 if (client.loggedIn && socket != null && outputStream != null
  394.                         && (highestPriority > 0 || client.stores[0] == null)) {
  395.                     sinceKeepAlive++;
  396.                     if (sinceKeepAlive > 500) {
  397.                         sinceKeepAlive = 0;
  398.                         inputBuffer[0] = (byte) 0;
  399.                         inputBuffer[1] = (byte) 0;
  400.                         inputBuffer[2] = (byte) 0;
  401.                         inputBuffer[3] = (byte) 10;
  402.                         try {
  403.                             outputStream.write(inputBuffer, 0, 4);
  404.                         } catch (IOException ioexception) {
  405.                             idleCycles = 5000;
  406.                         }
  407.                     }
  408.                 }
  409.             }
  410.         } catch (Exception exception) {
  411.             SignLink.reportError("od_ex " + exception.getMessage());
  412.         }
  413.     }
  414.  
  415.     public final void passiveRequest(int id, int type) {
  416.         if (client.stores[0] != null && fileVersions[type][id] != 0 && filePriorities[type][id] != 0
  417.                 && highestPriority != 0) {
  418.             OnDemandNode onDemandNode = new OnDemandNode();
  419.             onDemandNode.type = type;
  420.             onDemandNode.id = id;
  421.             onDemandNode.immediate = false;
  422.             synchronized (passiveRequests) {
  423.                 passiveRequests.insertBack(onDemandNode);
  424.             }
  425.         }
  426.     }
  427.  
  428.     public final OnDemandNode next() {
  429.         OnDemandNode onDemandNode;
  430.         synchronized (completed) {
  431.             onDemandNode = (OnDemandNode) completed.popTail();
  432.         }
  433.         if (onDemandNode == null) {
  434.             return null;
  435.         }
  436.         synchronized (immediateRequests) {
  437.             onDemandNode.clear();
  438.         }
  439.         if (onDemandNode.buffer == null) {
  440.             return onDemandNode;
  441.         }
  442.         int offset = 0;
  443.         try {
  444.             GZIPInputStream gzipinputstream = new GZIPInputStream(new ByteArrayInputStream(onDemandNode.buffer));
  445.             while (true) {
  446.                 if (offset == deflateOut.length) {
  447.                     throw new RuntimeException("buffer overflow!");
  448.                 }
  449.                 int readByte = gzipinputstream.read(deflateOut, offset, deflateOut.length - offset);
  450.                 if (readByte == -1) {
  451.                     break;
  452.                 }
  453.                 offset += readByte;
  454.             }
  455.         } catch (IOException ioexception) {
  456.             throw new RuntimeException("error unzipping");
  457.         }
  458.         onDemandNode.buffer = new byte[offset];
  459.         for (int position = 0; position < offset; position++) {
  460.             onDemandNode.buffer[position] = deflateOut[position];
  461.         }
  462.         return onDemandNode;
  463.     }
  464.  
  465.     public final int regIndex(int indexType, int regY, int regX) {
  466.         int localRegHash = (regX << 8) + regY;
  467.         for (int reg = 0; reg < regHash.length; reg++) {
  468.             if (regHash[reg] == localRegHash) {
  469.                 if (indexType == 0) {
  470.                     return regMapIndex[reg];
  471.                 }
  472.                 return regLandIndex[reg];
  473.             }
  474.         }
  475.         return -1;
  476.     }
  477.  
  478.     @Override
  479.     public final void request(int id) {
  480.         request(0, id);
  481.     }
  482.  
  483.     public final void setPriority(byte priority, int type, int id) {
  484.         if (client.stores[0] != null && fileVersions[type][id] != 0) {
  485.             byte[] data = client.stores[type + 1].get(id);
  486.             if (!verify(fileVersions[type][id], fileCrc[type][id], data)) {
  487.                 filePriorities[type][id] = priority;
  488.                 if (priority > highestPriority) {
  489.                     highestPriority = priority;
  490.                 }
  491.                 extrasTotal++;
  492.             }
  493.         }
  494.     }
  495.  
  496.     public final boolean landIndexExists(int landIndex) {
  497.         for (int index = 0; index < regHash.length; index++) {
  498.             if (regLandIndex[index] == landIndex) {
  499.                 return true;
  500.             }
  501.         }
  502.         return false;
  503.     }
  504.  
  505.     private final void remainingRequest() {
  506.         immediateRequestsSent = 0;
  507.         passiveRequestsSent = 0;
  508.         for (OnDemandNode onDemandNode = (OnDemandNode) sentRequests.getBack(); onDemandNode != null; onDemandNode = (OnDemandNode) sentRequests
  509.                 .getPrevious()) {
  510.             if (onDemandNode.immediate) {
  511.                 immediateRequestsSent++;
  512.             } else {
  513.                 passiveRequestsSent++;
  514.             }
  515.         }
  516.         while (immediateRequestsSent < 10) {
  517.             OnDemandNode onDemandNode = (OnDemandNode) toRequest.popTail();
  518.             if (onDemandNode == null) {
  519.                 break;
  520.             }
  521.             if (filePriorities[onDemandNode.type][onDemandNode.id] != 0) {
  522.                 extrasLoaded++;
  523.             }
  524.             filePriorities[onDemandNode.type][onDemandNode.id] = (byte) 0;
  525.             sentRequests.insertBack(onDemandNode);
  526.             immediateRequestsSent++;
  527.             sendRequest(onDemandNode);
  528.             expectData = true;
  529.         }
  530.     }
  531.  
  532.     public final void clearPassiveRequests() {
  533.         synchronized (passiveRequests) {
  534.             passiveRequests.clear();
  535.         }
  536.     }
  537.  
  538.     private final void localComplete() {
  539.         OnDemandNode onDemandNode;
  540.         synchronized (wanted) {
  541.             onDemandNode = (OnDemandNode) wanted.popTail();
  542.         }
  543.         while (onDemandNode != null) {
  544.             expectData = true;
  545.             byte[] buffer = null;
  546.             if (client.stores[0] != null) {
  547.                 buffer = client.stores[onDemandNode.type + 1].get(onDemandNode.id);
  548.             }
  549.             if (!verify(fileVersions[onDemandNode.type][onDemandNode.id], fileCrc[onDemandNode.type][onDemandNode.id],
  550.                     buffer)) {
  551.                 buffer = null;
  552.             }
  553.             synchronized (wanted) {
  554.                 if (buffer == null) {
  555.                     toRequest.insertBack(onDemandNode);
  556.                 } else {
  557.                     onDemandNode.buffer = buffer;
  558.                     synchronized (completed) {
  559.                         completed.insertBack(onDemandNode);
  560.                     }
  561.                 }
  562.                 onDemandNode = (OnDemandNode) wanted.popTail();
  563.             }
  564.         }
  565.     }
  566.  
  567.     private final void passivesRequest() {
  568.         while (true) {
  569.             if (immediateRequestsSent != 0) {
  570.                 break;
  571.             }
  572.             if (passiveRequestsSent >= 10) {
  573.                 break;
  574.             }
  575.             if (highestPriority == 0) {
  576.                 break;
  577.             }
  578.             OnDemandNode onDemandNode;
  579.             synchronized (passiveRequests) {
  580.                 onDemandNode = (OnDemandNode) passiveRequests.popTail();
  581.             }
  582.             while (onDemandNode != null) {
  583.                 if (filePriorities[onDemandNode.type][onDemandNode.id] != 0) {
  584.                     filePriorities[onDemandNode.type][onDemandNode.id] = (byte) 0;
  585.                     sentRequests.insertBack(onDemandNode);
  586.                     sendRequest(onDemandNode);
  587.                     expectData = true;
  588.                     if (extrasLoaded < extrasTotal) {
  589.                         extrasLoaded++;
  590.                     }
  591.                     message = "Loading extra files - " + extrasLoaded * 100 / extrasTotal + "%";
  592.                     passiveRequestsSent++;
  593.                     if (passiveRequestsSent == 10) {
  594.                         return;
  595.                     }
  596.                 }
  597.                 synchronized (passiveRequests) {
  598.                     onDemandNode = (OnDemandNode) passiveRequests.popTail();
  599.                 }
  600.             }
  601.             for (int type = 0; type < 4; type++) {
  602.                 byte[] priority = filePriorities[type];
  603.                 for (int id = 0; id < priority.length; id++) {
  604.                     if (priority[id] == highestPriority) {
  605.                         priority[id] = (byte) 0;
  606.                         onDemandNode = new OnDemandNode();
  607.                         onDemandNode.type = type;
  608.                         onDemandNode.id = id;
  609.                         onDemandNode.immediate = false;
  610.                         sentRequests.insertBack(onDemandNode);
  611.                         sendRequest(onDemandNode);
  612.                         expectData = true;
  613.                         if (extrasLoaded < extrasTotal) {
  614.                             extrasLoaded++;
  615.                         }
  616.                         message = "Loading extra files - " + extrasLoaded * 100 / extrasTotal + "%";
  617.                         passiveRequestsSent++;
  618.                         if (passiveRequestsSent == 10) {
  619.                             return;
  620.                         }
  621.                     }
  622.                 }
  623.             }
  624.             highestPriority--;
  625.         }
  626.     }
  627.  
  628.     public final boolean midiIndexEqualsOne(int index) {
  629.         return midiIndex[index] == 1;
  630.     }
  631. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement