1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. The 15th Mini-Mapping Contest came to an end. The Secrets of Warcraft 3 are soon to be revealed! Come and vote in the public poll for your favorite maps.
    Dismiss Notice
  3. The 12th incarnation of the Music Contest is LIVE! The theme is Synthwave. Knight Rider needs a song to listen to on his journey. You should definitely have some fun with this theme!
    Dismiss Notice
  4. Join other hivers in a friendly concept-art contest. The contestants have to create a genie coming out of its container. We wish you the best of luck!
    Dismiss Notice
  5. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Make a Map Work for Patch 1.24

Discussion in 'General Mapping Tutorials' started by PurgeandFire, Apr 29, 2010.

  1. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,427
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    Patch 1.24 Compatibility

    Table of Contents:


    Introduction:

    Once patch 1.24 was introduced, many maps have died and no longer work for patch 1.24+. Those maps used a particular bug known as the "return bug", which allowed you to typecast, or change the entity of one data type to another, using a simple method.

    Why was it fixed?

    Basically, people were toying around with WE a lot. Enough to use I2C and C2I by utilizing the return bug to allow bytecode to be executed in JASS. Then hackers would exploit this "feature" to execute bytecode in JASS to open the command prompt and implement a virus. Luckily though, bytecode shouldn't work anymore since wc3 apparently doesn't let arrays be fired as code anymore.

    Typecasting - The Return Bug

    Okay, well I talked a lot about this mysterious "Return Bug". What is it? It was a bug that functioned prior to patch 1.24. The key function that made the return bug useful was this:
    Code (vJASS):
    function H2I takes handle h returns integer
        return h
        return 0
    endfunction
     


    This famous bug was first exploited by SuperIKI followed by Peppar, and then was used in general in many systems from then on.

    What does it do?

    Well, it used to typecast variables. It would allow you to essentially transform the entity of the handle input into an integer. This allowed you to get the internal handle ID of that handle you've input, which is generally useful since it is unique for all but a few handles with different allocation methods. (eg: texttags/ubersplats) Now let me break it down to mention the key parts.

    Code (vJASS):
    function H2I takes handle h returns integer


    • H2I - This is the function's name, and refers to "Handle to Integer", following the same naming convention as the other X2Y functions that blizzard has. (I2S, R2I, etc.)
    • handle h - A handle is a data type which many of the types defined in wc3 will extend. Any
      type
      (example: unit, location, group) is a handle except for
      string
      ,
      real
      ,
      integer
      ,
      boolean
      , and
      code
      . By inputting a handle, you may input anything besides the types listed above, for example:
      Code (vJASS):
      globals
          integer test
      endglobals
      function H2I takes handle h returns integer
          return h
          return 0
      endfunction
      function Test takes nothing returns nothing
          set test = H2I(GetTriggerUnit()) //This will compile just fine since "unit" is a handle
          set test = H2I("My String")      //Will NOT compile, strings aren't handles.
      endfunction
    • returns integer - Okay, so far we take in handle h, now to complete the H2I cycle, we must return an integer. (Hence, H2I) Basically, once we define a return, the function will be considered a valid input for the returned type. For example:
      Code (vJASS):
      function ReturnString takes unit u returns string
          return GetUnitName(u)+"Whee"
      endfunction
      function Test takes nothing returns nothing
          local string s = ReturnString(GetTriggerUnit())
          //This will compile just fine, since the "ReturnString" function returns a string-type.
          local unit u   = ReturnString(GetTriggerUnit())
          //This will NOT compile, it returns a string, not a unit-type.
      endfunction

    Now:
    Code (vJASS):
        return h
        return 0


    return
    is the GUI equivalent to Skip Remaining Actions. Once you define a type after the return, that means the function also must return that type defined.
    Code (vJASS):
    function ReturnGood takes nothing returns unit
        return GetTriggerUnit() //Compiles fine, since you state to return a unit and do just that.
    endfunction
    function ReturnBad takes nothing returns nothing
        return GetTriggerUnit() //error! You declare that you "return nothing" but you return a unit.
    endfunction


    The thing peculiar about
    return
    prior to patch 1.24 though, was that the compiler checked only the last return defined. So as long as you return the correct type at the end, the previous returns will work as well. Example:
    Code (vJASS):
    function CompiledFinePriorToPatch takes nothing returns string
        set bj_lastCreatedUnit = CreateUnit(Player(15),'hfoo',0,0,0)
        if GetUnitUserData(bj_lastCreatedUnit)==50 then
            return bj_lastCreatedUnit //we return a unit instead of a string???!
        endif
        return "This compiles!" //now we return a string.
    endfunction


    The compiler used to check only
    return "This compiles!"
    to see if it was returning the correct data type. Obviously, this is just an example that does absolutely nothing, but it just shows how it could be abused.

    Now, let's go back to the return h/return 0. If we were to define only
    return h
    , it would show a compile error since it is supposed to return an integer.
    Code (vJASS):
    function H2I takes handle h returns integer
        return h //error! You returned the wrong type!
    endfunction


    But when we add the final return, it will become "okay" for the compiler, and they'll think it is just fine.
    Code (vJASS):
    function H2I takes handle h returns integer
        return h
        return 0 //The compiler ignores "return h", and just focuses on this.
    //hmm... 0 seems like an integer to me, so it works fine **
    //** prior to patch 1.24
    endfunction


    The cool thing though, is that
    return 0
    is never executed. It skips the remaining actions after the
    return h
    , so
    return 0
    is never executed at all.

    When doing this, it allowed us to successfully typecast from a handle to an integer. Now, if you were to return an integer of a handle, what would it return? Well, the way normally allocated handles work is that they have specific handle ids. When a new handle is declared, it will be given a new unique handle ID. The way they are assigned is by the showing that it is the nth handle declared. They go from order, from
    0x100000
    (or 1048576), and then it goes +1 for each new handle declared. So say you have a handle, you can check how many handles were declared before it by using this function:
    Code (vJASS):
    function GetHandleIdPosition takes handle h returns integer
        return H2I(h)-0x100000 //or 1048576
    //No longer works as of patch 1.24
    //Thus, you'd really use "return GetHandleId(h)-0x100000", the new native.
    endfunction


    Now, since the handles (besides texttags and ubersplats etc.) had unique ID's, this made it perfect for spells. And thus began the era of:
    Local Handle Vars

    Local Handle Vars was the most widely used system in JASS of that time, and relied solely on gamecache for timer attachment purposes. It uses H2I, but that wasn't the only part of the return bug it used. Let's first look at the function for attaching a handle:
    Code (vJASS):
    function SetHandleHandle takes handle subject, string name, handle value returns nothing
        if value==null then
            call FlushStoredInteger(LocalVars(),I2S(H2I(subject)),name)
        else
            call StoreInteger(LocalVars(), I2S(H2I(subject)), name, H2I(value))
        endif
    endfunction
     


    Ok, this stores an integer into LocalVars() [a defined gamecache]. Gamecache are much like hashtables, where they took a parent string name as a "category" and then the "name". It is analogous to a computer. We store files into a unique folder (represents the category) and store the child file (represents "name"). Then we assign the value. Now, since we couldn't manually assign a handle, we would assign the ID of the handle instead. It was a really neat way to attach objects, but you must think, "How do we get the data back"? Well, we use the same method pretty much:
    Code (vJASS):
    function GetHandleHandle takes handle subject, string name returns handle
        return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
        return null
    endfunction
     


    Now, it expects us to return a "handle". Since all handles can have a null value, we can return "null" at the end and it will suffice. Now, we return the integer, and it expects a handle to be returned. Well, there is an H2I, why not an I2H? Well, this is essentially the technique for an I2H.
    Code (vJASS):
    function I2H takes integer i returns handle
        return i
        return null
    endfunction


    That is essentially the return bug wrapped up. So why are maps broken? Well, almost all JASS spells at the time used local handle vars or CSCache, which means that since the return bug no longer works, the functions no longer work. In fact, it will give errors now if you try to use this method, that is the point of this tutorial. To fix it.

    Patch 1.24:

    Both a killer and a revolutionary patch. In that patch, they removed the return bug, but added a great replacement:
    Code (vJASS):
    native GetHandleId takes handle h returns integer


    This is the new H2I function, except it is a native so it is very fast in comparison. We don't have an I2H, but why need that when we have hashtables? Hashtables are the revolutionary feature of patch 1.24, which allows us to store the data types we need without being restricted to integers, strings, reals, and booleans.

    However, hashtables also opened a new way to type-cast:
    Code (vJASS):
    globals
        hashtable MyHash = InitHashtable()  
    endglobals
    function Widget2Unit takes widget w returns unit
        call SaveWidgetHandle(MyHash,0,0,w)
        return LoadUnitHandle(MyHash,0,0)
    endfunction


    To type cast I2X, you'd use:
    Code (vJASS):
    globals
        hashtable MyHash = InitHashtable()
    endglobals
    function Integer2Widget takes integer int returns widget
        call SaveFogStateHandle(MyHash,0,0,ConvertFogState(int))
        return LoadWidgetHandle(MyHash,0,0)
    endfunction


    It uses fogstates and basically exploits hashtables, but this isn't very useful any more now that we have the glorious hashtables to replace the older systems. (Only W2U is useful in specific cases)

    But that isn't the thing. We want to know how to fix maps that used the old systems. But with this knowledge (don't worry, it isn't pointless) you'll be able to do that easily.

    Fixing an Old Map:

    Hooray, we finally get to fix an old map!

    Ok, now as we have discussed, there are a few systems that cause this:
    • CSCache/Caster System
    • Local Handle Vars
    • ABCt - (Resolve: Use TimerTicker or a different timer system)
    • TimerTicker - (Use the compatibility version)
    • Old versions of Table (included with CSCache)
    • Cinematic System v1.5 or lower (Resolve: Use v1.5b)
    • HAIL
    • Graphics System
    • Decal
    • T2Ix
    • Unit Get Local (Deprecated Local Handle Vars)
    • Class System
    • Spell Classes System
    • Handle Lists
    • HSAS
    • Special Events/Alternative Spell Templates System

    There are more, but that gets a lot of them. All but the ones with a "Resolve" use H2I and don't have an upgrade. (Except for CSCache/Caster System/Table but those use vJASS, so you might not want to use it)

    Fixes:

    Before I get into fixing these problems manually, I recommend giving this automated-tool a shot:
    Handlevars Return Casting Converter
    If that doesn't work, try some of the workarounds mentioned below!

    Okay, let's get to one major fix that will probably knock off a lot of them.

    Replace:
    Code (vJASS):
    function H2I takes handle h returns integer
        return h
        return 0
    endfunction

    With:
    Code (vJASS):
    function H2I takes handle h returns integer
        return GetHandleId(h)
    endfunction


    That will fix most of the systems.

    The fix to CSCache and Handle Vars is this:
    Faux Handle Vars & CSCache

    It uses hashtables to store everything instead of gamecache, and just uses GetHandleId() to replace it, and uses the hashtable API for everything else. It allows you to use the same naming convention as the previous local handle vars & CSCache so you don't have to make changes later.

    Let's observe some of the functions:
    Code (vJASS):
        globals
            hashtable ht
        endglobals
        function SetHandleHandle takes agent subject,  string label, agent value returns nothing
            if(value==null) then
                call RemoveSavedHandle( ht, GetHandleId(subject), StringHash(label))
            else
                call SaveAgentHandle( ht, GetHandleId(subject), StringHash(label), value)
            endif
        endfunction
        function GetHandleUnit takes agent subject, string label returns unit
            return LoadUnitHandle( ht, GetHandleId(subject), StringHash(label))
        endfunction


    What this does is it saves an agent handle (the ones we use/create, essentially) and then the 2nd function returns a unit that was saved. Basically, they use GetHandleId() instead of H2I(), and they use StringHash() to convert it an integer input for the hashtable functions.

    To implement Faux HandleVars & CSCache, copy the respective library, and paste it into the map's header. (The map header is found by opening the trigger editor, and clicking on the map's name listed in the left window) Then it should say "Custom Script Code" and just replace the old handle vars/CScache with the new ones.

    Now it is all fine for CSCache and Handle Vars, but some maps might use things like the Class System, or generally systems that use I2X.

    Let's look at some functions of the Class System by AIAndy:
    Code (vJASS):
    function Handle2Int takes handle h returns integer
      return h
      return 0
    endfunction

    function Int2Group takes integer i returns group
      return i
      return null
    endfunction

    function Int2Unit takes integer i returns unit
      return i
      return null
    endfunction

    function Int2Location takes integer i returns location
      return i
      return null
    endfunction

    function Int2Effect takes integer i returns effect
      return i
      return null
    endfunction

    function Int2Timer takes integer i returns timer
      return i
      return null
    endfunction

    function Int2Terraindeformation takes integer i returns terraindeformation
      return i
      return null
    endfunction

    function Int2Destructable takes integer i returns destructable
      return i
      return null
    endfunction

    function Int2Trigger takes integer i returns trigger
      return i
      return null
    endfunction
     


    How do you think we should fix this? Well, if you look above, you'll realize we can now use fogstates along with hashtables for I2X functions. Thus, we'd end up getting this:
    Code (vJASS):
    globals
        hashtable MyHash = InitHashtable()
    endglobals

    function Handle2Int takes handle h returns integer
        return GetHandleId(h)
    endfunction

    function Int2Group takes integer i returns group
        call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
        return LoadGroupHandle(MyHash,0,0)
    endfunction

    function Int2Unit takes integer i returns unit
        call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
        return LoadUnitHandle(MyHash,0,0)
    endfunction

    function Int2Location takes integer i returns location
        call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
        return LoadLocationHandle(MyHash,0,0)
    endfunction

    function Int2Effect takes integer i returns effect
        call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
        return LoadEffectHandle(MyHash,0,0)
    endfunction

    function Int2Timer takes integer i returns timer
        call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
        return LoadTimerHandle(MyHash,0,0)
    endfunction

    //function Int2Terraindeformation takes integer i returns terraindeformation
    //    call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
    //    return LoadGroupHandle(MyHash,0,0)
    //endfunction
    //Lol there is no save/load for terrain deformations, so we are stuck.
    //There is another way to typecast, known as the "Return Nothing" bug [discovered by Deaod]
    //However, it no longer works.

    function Int2Destructable takes integer i returns destructable
        call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
        return LoadDestructableHandle(MyHash,0,0)
    endfunction

    function Int2Trigger takes integer i returns trigger
        call SaveFogStateHandle(MyHash,0,0,ConvertFogState(i))
        return LoadTriggerHandle(MyHash,0,0)
    endfunction


    Basically, we've succesfully completed it for all but the terrain deformation. Sadly, that one no longer works. There are a handful of types we can't save, but we don't use it that much anyway. The only real reason to save a terraindeformation is to stop it anyway, so you'd need to manually fix that or just generally remove the functions. The only types that support typecasting at the moment are:
    Code (vJASS):
    real
    string
    integer
    boolean
    player
    widget
    destructable
    item
    unit
    ability
    timer
    trigger
    triggercondition
    triggeraction
    event
    force
    group
    location
    rect
    boolexpr
    sound
    effect
    unitpool
    itempool
    quest
    questitem
    defeatcondition
    timerdialog
    leaderboard
    multiboard
    multiboarditem
    trackable
    dialog
    button
    texttag
    lightning
    image
    ubersplat
    region
    fogstate
    fogmodifier
    agent
    hashtable


    The rest can't since there are no hashtable functions for them. However, this system can help fix that problem, for most of them (except
    weathereffect
    and
    terraindeformation
    ):
    http://www.thehelper.net/forums/showthread.php/152826-Handle

    After you have fixed the functions, and your map saves, you may still experience some problems. Perhaps your map will throw an immediate error upon testing the map or something along those lines. Now that the map can be saved, you will need to troubleshoot the problem by disabling triggers until your map loads. Once your map loads, hopefully you will be able to narrow down which trigger caused the problem. In general, it may be harder to fix that trigger opposed to just remaking it, so I recommend that you remake whatever does not function properly.

    Conclusion:

    Hopefully you may now fix your map. Generally, Faux Handle Vars & CSCache will usually fix a map with ease, but sometimes it isn't enough if they use special systems. Generally, it is just good to know how to fix them, since it will help you to refrain from using the old systems as well.

    Credits:
    • SuperIKI and Peppar and others for H2I.
    • Vexorian for Faux Handle Vars and CSCache
    • Jesus4Lyf for some of the malicious bytecode info
    • AIAndy for making a system that can no longer work in 1.24. =P (since the typecasting methods don't work for terraindeformations)
    • kingkingyyk3 for a quick reference to the X2Y and I2X functions.
     
    Last edited: Mar 12, 2018
  2. Element of Water

    Element of Water

    Joined:
    Aug 3, 2008
    Messages:
    2,298
    Resources:
    5
    Spells:
    3
    Tutorials:
    1
    JASS:
    1
    Resources:
    5
    This is a little late, but I guess it could be helpful...
     
  3. Darkness-4ever

    Darkness-4ever

    Joined:
    Nov 4, 2007
    Messages:
    763
    Resources:
    0
    Resources:
    0
    Very good tutorial, and thorough in its explanation and fixes, I expect if any tutorial is worth the time and effort of an early review by the moderator, this would definitely be it, though it would have been of great use had it been around earlier. A shame some things are impossible to fix now that the return bug is gone, but hopefully this tutorial will allow people to salvage what they can from maps that had died, sadly one of my own maps used the return bug to takes a real and return a location, was devastating to have to abandon that map after the patch, but oh well moving on, great tutorial, good work.
     
  4. Septimus

    Septimus

    Joined:
    May 3, 2008
    Messages:
    4,176
    Resources:
    49
    Packs:
    1
    Maps:
    35
    Spells:
    8
    Tutorials:
    5
    Resources:
    49
    I second that. :p

    and i estimate that all map should have been fix by now, since patch 1.24 had been release for quite a time. :D
     
  5. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,427
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    Yeah, I was a little late. I just never thought of making a tutorial for this until now. I still see an occasional question for this though, so maybe it'll still be of use. And at least it is still good to know. =D

    >sadly one of my own maps used the return bug to takes a real and return a location

    Arghh... I'm too late for everything. D=
     
  6. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,427
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    I'm going to decide to approve this. After all, no one else really moderates this place since it is now my duty.

    If anyone has any objections to this, or if they need help with something regarding this tutorial, or just anything in general, please notify me. =)
     
  7. Streamlife

    Streamlife

    Joined:
    Oct 18, 2004
    Messages:
    14
    Resources:
    0
    Resources:
    0
    How to Fix the Return Bug!¿

    Hello All there, well this is my problem... Like we Know, (About the return bug), Dont leave host Old maps , so i have this hash in one map (and just need the exacly config) to fix it.! mayb some here know about it


    Code (vJASS):
    function GetTrigger takes gamecache cache ,string string1,string string2 returns trigger
    return GetStoredInteger(cache,string1,string2)
    return null
    endfunction

    function GetUnit takes gamecache cache ,string string1,string string2 returns unit
    return GetStoredInteger(cache,string1,string2)
    return null
    endfunction


    function H2I takes handle h returns integer
    return h
    return 0
    endfunction

    function H2S takes handle h returns string
    return I2S(H2I(h))
    endfunction

    function H2T takes handle h returns timer
    return h
    endfunction

    function I2H takes integer i returns handle
    return i
    return null
    endfunction

    function I2G takes integer i returns group
    return i
    return null
    endfunction

    function ErrorMessage takes player whichplayer,string text returns nothing
    local sound s=CreateSoundFromLabel("InterfaceError",false,false,false,10,10)
    if(GetLocalPlayer()==whichplayer)then
    if(text!="")and(text!=null)then
    call ClearTextMessages()
    call DisplayTimedTextToPlayer(whichplayer,0.52,-1.00,2.00,"|cffffcc00"+text+"|r")
    endif
    call StartSound(s)
    endif
    call KillSoundWhenDone(s)
    set s=null
    endfunction


    and here the Photo http://img837.imageshack.us/img837/772/jassp.jpg

    Thanks so much!

    ( Another question.. this map have that jass Text. but Wehn i open anothers maps Cant Found this chain Jass text So the question is, Where need Search it?---

    ( mean this Chain function H2I takes handle h returns integer
    return h
    return 0
    endfunction ) Thanks so much since now!
     
    Last edited: Oct 7, 2010
  8. ToBeDetermined

    ToBeDetermined

    Joined:
    Feb 10, 2014
    Messages:
    9
    Resources:
    0
    Resources:
    0
    Fixing an Old Map

    Can't do it myself since I'm on a Mac running some software that disables harddrive partitioning so no bandcamp or anything.....So could someone do me a huge favor and fix this map for me?

    Also, I believe I tried some of the methods listen in this post on the map before, and they were ineffective. :(
     
  9. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,427
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    Which map? You should attach it to your post, or put it on pastebin, or post a URL link to it.

    Also, the conversion isn't perfectly 1-1. There are some functions that aren't able to be created anymore (e.g. SetHandleHandle); this will mostly help you get rid of syntax errors so the map can be opened in wc3 (and usually to recode it). Some cases are worse than others (generally, the mostly-GUI ones are easier to fix because they rarely used the bug, but the JASS maps take a little more work). It varies.
     
  10. ToBeDetermined

    ToBeDetermined

    Joined:
    Feb 10, 2014
    Messages:
    9
    Resources:
    0
    Resources:
    0
    Sorry

    Guess when I tried to attach the map it didn't go through. I apologize.

    Thanks for responding so quickly by the way. :D I'll try to reattach it in this post.
     

    Attached Files:

  11. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,427
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    I can't edit it at the moment because I too am on a Mac. I do see the issue though. The user used WEU functions, which in turn used its own set of typecasting and a similar form of local handle vars. There are about 23 instances of it and a couple of functions. It shouldn't take that long to fix. Hopefully it'll still work.

    There might be a few other oddities (sometimes functions are designed poorly and might not return anything), I'll try to figure it out this weekend when I get back to my PC. :) If you could send me a visitor message or PM as a reminder on Friday, that'd be very helpful (I have a tendency to forget).
     
  12. ToBeDetermined

    ToBeDetermined

    Joined:
    Feb 10, 2014
    Messages:
    9
    Resources:
    0
    Resources:
    0
    Thanks!

    Yeah I can do that no problem. Thanks for your help!

    By the way, I think there should be a section for recruiting help for certain things. Like fixing old maps, testing new ones, other things..... Makes sense to me especially when you already have a rep system in place enticing people TO help.....
     
  13. doido

    doido

    Joined:
    Jul 4, 2016
    Messages:
    2
    Resources:
    0
    Resources:
    0
    I'm not a english speaker, for me is too hard fix my map. Can anyone with a good soul fix it to me? I love this map, but I can not play it.

    Please. Please. Please.

    Thank you. Thank you. Thank you.

    We have to use worldedit to fix?
     

    Attached Files:

    Last edited: Jul 4, 2016