don't click here

Basic Questions & Answers thread

Discussion in 'Engineering & Reverse Engineering' started by Tweaker, May 29, 2008.

  1. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    And it's me with my Hill Top 2P queries again.

    I've got Act 1 working semi-competently now, Act 2 however has a key roadblock, about halfway in it stops loading the objects for the level.

    Well more accurately, it loads the objects only after a player has went past their location. When you turn back to meet it onscreen from the left, there it is.

    Anyone know what might be the source of such behaviour? I get HTZ 2 has a load more objects than VS can perhaps handle but it seems here like the issue isn't loading them altogether just WHEN it does it.
     
  2. AppleSauce

    AppleSauce

    It's now illegal to use your meme! Member
    44
    2
    8
    What are you using to add sprites, SonMapEd or Flex 2? Also, have you expanded Sonic's art limit, or converted it to a DMA queue yet?

    Edit: I just realised your problem has been solved, sorry.
     
    Last edited: Jul 1, 2020
  3. Trance

    Trance

    The annoying guy you've probably seen around. Member
    32
    2
    8
    I used Tile Molester.
     
  4. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    Okay, now I've got Act 2 of Hill Top running in VS. One issue though. The signpost I added in doesn't work. It just causes a Game Over.

    Anyone know how to install a signpost into a 2P version of a level?

    EDIT: Okay looking further turns out that was a half truth. It works fine if Tails crosses first, but Sonic crossing first glitches up Tails' screen and causes a Game Over. That specify the problem any better?


    Also I've made a 'sorta kinda' step into fixing HTZ's background in split screen:
    [​IMG]

    Here the art is back and the far mountains are still like I was aiming for, but the far clouds are still dynamic on Sonic's screen, and I think that may be what is causing the SLIGHT corruption here. :P

    I kinda did a blind shot in the dark here, having no idea how the code actually works. Anyone got a better idea how to go about this?

    Code (Text):
    1.  
    2. Dynamic_HTZ:
    3.     tst.w    (Two_player_mode).w
    4.     bne.w    Dynamic_HTZ_2P
    5.     lea    ($FFFFF7F0).w,a3
    6.     moveq    #0,d0
    7.     move.w    (Camera_X_pos).w,d1
    8.     neg.w    d1
    9.     asr.w    #3,d1
    10.     move.w    (Camera_X_pos).w,d0
    11.     move.w    d0,d2
    12.     andi.w    #$F,d2
    13.     seq.b    d2
    14.     ext.w    d2
    15.     lsr.w    #4,d0
    16.     add.w    d1,d0
    17.     add.w    d2,d0
    18.     subi.w    #$10,d0
    19.     divu.w    #$30,d0
    20.     swap    d0
    21.     cmp.b    1(a3),d0
    22.     beq.s    BranchTo_loc_3FE5C
    23. Dynamic_HTZ_2P:
    24.     move.b    d0,1(a3)
    25.     move.w    d0,d2
    26.     andi.w    #7,d0
    27.     add.w    d0,d0
    28.     add.w    d0,d0
    29.     add.w    d0,d0
    30.     move.w    d0,d1
    31.     add.w    d0,d0
    32.     add.w    d1,d0
    33.     andi.w    #$38,d2
    34.     lsr.w    #2,d2
    35.     add.w    d2,d0
    36.     lea    word_3FD9C(pc,d0.w),a4
    37.     moveq    #5,d5
    38.     move.w    #-$6000,d4
    39.  
    40. loc_3FD7C:
    41.     moveq    #-1,d1
    42.     move.w    (a4)+,d1
    43.     andi.l    #$FFFFFF,d1
    44.     move.w    d4,d2
    45.     moveq    #$40,d3
    46.     jsr    (QueueDMATransfer).l
    47.     addi.w    #$80,d4
    48.     dbf    d5,loc_3FD7C
    49.  
    50. BranchTo_loc_3FE5C:
    51.     bra.w    loc_3FE5C
     
    Last edited: Jul 2, 2020
  5. Smimbo

    Smimbo

    Member
    3
    0
    1
    Hi, I am looking for guidance on how I can create mods for Sonic Generations in reference to importing models and music into the game to be loaded up using the mod loader, I have looked online through modding tutorials, however I am new to modding so some of it isn't very clear to me, so if anyone would be able to help me in modding the game it would be greatly appreciated.
     
  6. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    Okay progression. I've managed to figure out the issue with the background, apparently the VRAM is interfering with some of the split screen data in the location.

    I know how to move the background tiles' VRAM location, but not what location the game projects, anyone else know from that bit of code above and this one below?

    Code (Text):
    1.  
    2. loc_3FE5C:
    3.     lea    ($FFFFA800).w,a1 ;A800
    4.     tst.w    (Two_player_mode).w
    5.     bne.w    Dynamic_HTZ_2P_2
    6.     move.w    (Camera_X_pos).w,d2
    7.     neg.w    d2
    8.     asr.w    #3,d2
    9. Dynamic_HTZ_2P_2:
    10.     move.l    a2,-(sp)
    11.     lea    (ArtUnc_HTZClouds).l,a0
    12.     lea    ($FFFF7C00).l,a2 ;7C00
    13.     moveq    #$F,d1
    14.     jsr    Adjust2PArtPointer
    15.  
    16. loc_3FE78:
    17.     move.w    (a1)+,d0
    18.     neg.w    d0
    19.     add.w    d2,d0
    20.     andi.w    #$1F,d0
    21.     lsr.w    #1,d0
    22.     bcc.s    loc_3FE8A
    23.     addi.w    #$200,d0
    24.  
    25. loc_3FE8A:
    26.     lea    (a0,d0.w),a4
    27.     lsr.w    #1,d0
    28.     bcs.s    loc_3FEB4
    29.     move.l    (a4)+,(a2)+
    30.     adda.w    #$3C,a2
    31.     move.l    (a4)+,(a2)+
    32.     adda.w    #$3C,a2
    33.     move.l    (a4)+,(a2)+
    34.     adda.w    #$3C,a2
    35.     move.l    (a4)+,(a2)+
    36.     suba.w    #$C0,a2
    37.     adda.w    #$20,a0
    38.     dbf    d1,loc_3FE78
    39.     bra.s    loc_3FEEC
    40. ; ===========================================================================
    41.  
    42. loc_3FEB4:
    43.     move.b    (a4)+,(a2)+
    44.     move.b    (a4)+,(a2)+
    45.     move.b    (a4)+,(a2)+
    46.     move.b    (a4)+,(a2)+
    47.     adda.w    #$3C,a2
    48.     move.b    (a4)+,(a2)+
    49.     move.b    (a4)+,(a2)+
    50.     move.b    (a4)+,(a2)+
    51.     move.b    (a4)+,(a2)+
    52.     adda.w    #$3C,a2
    53.     move.b    (a4)+,(a2)+
    54.     move.b    (a4)+,(a2)+
    55.     move.b    (a4)+,(a2)+
    56.     move.b    (a4)+,(a2)+
    57.     adda.w    #$3C,a2
    58.     move.b    (a4)+,(a2)+
    59.     move.b    (a4)+,(a2)+
    60.     move.b    (a4)+,(a2)+
    61.     move.b    (a4)+,(a2)+
    62.     suba.w    #$C0,a2
    63.     adda.w    #$20,a0
    64.     dbf    d1,loc_3FE78
    65.  
    66. loc_3FEEC:
    67.     move.l    #$FF7C00,d1 ;7C00
    68.     move.w    #-$7B00,d2 ;-$5D00
    69.     move.w    #$80,d3
    70.     jsr    (QueueDMATransfer).l
    71.     movea.l    (sp)+,a2
    72.     addq.w    #2,a3
    73.     bra.w    loc_3FF30
     
  7. Cyber Axe

    Cyber Axe

    Oldbie
    I've tried several times over the last 2 weeks to get this working properly (https://info.sonicretro.org/SCHG_How-to:Port_Sonic_2's_Level_Art_Loader_to_Sonic_1), I had similar problems when trying to implement the Fast Loading stuff Aurora Fields had (https://forums.sonicretro.org/index.php?threads/s1-considerably-speeding-up-level-loading.33616/), at this point i'm starting to wonder if the re-compressed tiles are at fault, i used KENSSharp to decompress every nemesis file and re-compress in several formats, only the nemesis code seems to work, I've tried searching the forum to see if anyone else has had similar problems to no avail, the graphics always end up garbled.

    I'm about 90% sure the problems are with the ProcessDMAQueue part, though i may be wrong (i'm also certain i had it working fine at one point then after changing other code and going back it stopped working, though i have commented out all the other code and disabled all other things I've implemented so it may just be that I've missed something.), I'd actually like to implement https://github.com/flamewing/ultra-dma-queue however the code seems completely incompatible

    I've uploaded my code to github if anyone wants to take a look https://github.com/TrueCyberAxe/Sonic-1-Enhanced

    I've been using http://geoo.digibase.ca/Genesis/s1_labellist.txt to figure out how to map the older subroutines from the guide to the github code

    Any advice or insight would be appreciated, thanks.
     
    Last edited: Jul 3, 2020
  8. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    Whoops, now I remember what happened.

    The 2P glitch was caused by me trying to improvise a levevents to branch over the HTZ boss like the other 3 levels do in 2 player.

    Code (Text):
    1. ; ---------------------------------------------------------------------------
    2. loc_HTZ_2P2:
    3.     move.w    #$2D80,(Camera_Max_X_pos).w
    4.     move.w    #$2D80,(Tails_Max_X_pos).w
    5.  
    6.     rts
    Problem is this just puts Tails straight to Sonic's location when he hits the screen end area. Putting the branch later in the routine prevents a Game Over but the same problem still occurs. Is there some info I'm missing from the three proper levels' approach to this?
     
  9. Fred

    Fred

    Taking a break Oldbie
    1,563
    117
    43
    Portugal
    Sonic 3 Unlocked
    All that code is doing is setting where the end of the level is. Somewhere, the min X position is also being set, and when that happens Tails gets pushed to the end of the level to stay inside the boundaries of the screen.

    If the max Y position is also set, then that will likely also place Tails below the bottom of the level, causing him to die, and then probably respawn back underneath the bottom of the level in a cycle, leading to a game over.

    Look for places where those camera values are manipulated (did you remove the boss object from the layout?) and do whatever makes sense, like only locking Sonic's camera or not bothering with the vertical lock at all.
     
  10. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    Well the way they seem to do it for the main three levels is if it's two player then branch over the whole routine. I tried doing that and got the same problems.

    EDIT: No wait, fixed it. Didn't notice a sneaky branch all the way at the very top before the index.
     
    Last edited: Jul 4, 2020
  11. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    Sorry to bug again, but I'm so damn close to getting this thing looking perfect and this roadblock is driving me crazy. I'm trying to move the VRAM location of the dynamic background in Hill Top Zone. I get that it's connected to Dynamic_HTZ and I know how to move the tiles around, but not where the plane detects them, they're ALWAYS on $A000 no matter what I edit. Does anyone know what I'm supposed to do to move this around, anything to do with this?

    Code (Text):
    1.  
    2.     lea    word_3FD9C(pc,d0.w),a4
    3.     moveq    #5,d5 ;5
    4.     move.w    #-$6000,d4 ;-6000
    5.  
    6. loc_3FD7C:
    7.     moveq    #-1,d1
    8.     move.w    (a4)+,d1
    9.     andi.l    #$FFFFFF,d1 ;$FFFFFF
    10.     move.w    d4,d2
    11.     moveq    #$40,d3 ;$40
    12.     jsr    (QueueDMATransfer).l
    13.     addi.w    #$80,d4 ;$80
    14.     dbf    d5,loc_3FD7C
    15.  
    16.  
    17. BranchTo_loc_3FE5C:
    18.     bra.w    loc_3FE5C
    19. ; ===========================================================================
    20. ; HTZ mountain art main RAM addresses?
    21. word_3FD9C:
    22.     dc.w   $80, $180, $280, $580, $600, $700    ; 6
    23.     dc.w   $80, $180, $280, $580, $600, $700    ; 12
    24.     dc.w  $980, $A80, $B80, $C80, $D00, $D80    ; 18
    25.     dc.w  $980, $A80, $B80, $C80, $D00, $D80    ; 24
    26.     dc.w  $E80,$1180,$1200,$1280,$1300,$1380    ; 30
    27.     dc.w  $E80,$1180,$1200,$1280,$1300,$1380    ; 36
    28.     dc.w $1400,$1480,$1500,$1580,$1600,$1900    ; 42
    29.     dc.w $1400,$1480,$1500,$1580,$1600,$1900    ; 48
    30.     dc.w $1D00,$1D80,$1E00,$1F80,$2400,$2580    ; 54
    31.     dc.w $1D00,$1D80,$1E00,$1F80,$2400,$2580    ; 60
    32.     dc.w $2600,$2680,$2780,$2B00,$2F00,$3280    ; 66
    33.     dc.w $2600,$2680,$2780,$2B00,$2F00,$3280    ; 72
    34.     dc.w $3600,$3680,$3780,$3C80,$3D00,$3F00    ; 78
    35.     dc.w $3600,$3680,$3780,$3C80,$3D00,$3F00    ; 84
    36.     dc.w $3F80,$4080,$4480,$4580,$4880,$4900    ; 90
    37.     dc.w $3F80,$4080,$4480,$4580,$4880,$4900    ; 96
    38. ; ===========================================================================

    Also I've figured out some bugs caused with objects duplicating in two player mode. Is there any sort of branch to make the object recognise if there's a duplicate of itself in the exact same location? That way I can tell it not to spawn so long as there's another one already there.
     
  12. MarkeyJester

    MarkeyJester

    Original, No substitute Resident Jester
    2,201
    431
    63
    Japan
    The "move.w #-$6000,d4 ;-6000" is what you want, -$6000 is actually $A000 in signed negative. -$6000 is just another way of representing it. Remove the minus symbol, and make this word whatever VRAM address you want.
     
  13. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    I tried that but like I said, it only moves the actual tiles, not where the plane directs it. eg. If I put -$7000, it moves the tiles up to $9000 but the scrolling plane still shows the tiles at $A000.
     
  14. Fred

    Fred

    Taking a break Oldbie
    1,563
    117
    43
    Portugal
    Sonic 3 Unlocked
    I don't understand the question. Are we talking about the blue mountains in the far BG? Those are part of the background blocks/chunks, aren't they? Did you edit the block definitions to point at the new tile ID/VRAM addresses?

    Also, I should point out that if you want to get the parallax effect working for both players, you're going to have to DMA the mountain tiles to two different places in VRAM, one for each camera, and then have each screen draw a different set of background chunks, one pointing at one VRAM address and the other one at the other. This is not an easy job; the only level that does this in Sonic 3 is Desert Palace, open that up in SonLVL to see what I mean.
     
  15. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    Yeah the blue mountains plus the far back clouds, I've actually managed to edit the routine so it stays still, meaning no conflict between either player screens. However is that they're in the same location as some split screen data ($A000) so activating them corrupts 2 player's screen.

    The roadblock is that I don't know how to edit the block definitions. Even SonicLVL doesn't seem to be able to locate above $2FF while I've relocated to them to $E800 (which I'm even in short terming is above $400). Plus I want the definitions to alter between both one player and two player modes really. Is there anything in the asm that can edit the block definitions so they look for $E800 onward instead of $A000?
     
  16. MarkeyJester

    MarkeyJester

    Original, No substitute Resident Jester
    2,201
    431
    63
    Japan
    Did you edit the actual BG's block mappings in a level editor of some kind (say SonLVL)? If not, that'll be why...
     
    Last edited: Jul 6, 2020
  17. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    The thing is last time I tried to edit that section it just completely screwed up the entire tile set and I had to start all over.

    And again, I need to somehow make this work for two different sections due to 1 player and 2 player using different VRAM areas (unless this is solely an edit to the actual 128 mappings, in which case I could maybe redirect to an edited duplicate for two player?).

    EDIT: I've tried this again with somewhat less disastrous results, however I'm having trouble processing how the Block format works with the imported tiles (ie. the orange cross blocks). It doesn't seem to format which tiles in the VRAM to load for it and when I try it just ruins the previous selected block instead.

    EDIT 2: Never mind. Managed to work around it. Just one bug left! :D

    If this one is out of range, I'll pass one fix for another. Some of the objects in HTZ are loading multiple times in splitscreen, seemingly for each time either player comes within range of them (though that seems to include other cases that didn't count in 1p mode before like crossing them from far above or below). If I'm not mistaken some objects in normal multiplayer levels suffer this issue as well. I believe I saw the Crawlton badnik in MCZ in duplicates when in 2P.

    This of course can consume the object number in HTZ, especially since it does it for a ton of awkwardly placed objects (the player lagging behind can come before a three or four headed Rexon for example, or FIVE airlifts overlapped with each other), so I need to figure out what the issue is here.
     
    Last edited: Jul 6, 2020
    • Informative Informative x 1
    • List
  18. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    Okay I've finally got the VRAM moved where I want....only of course that means now that one player's location is screwed.

    Since the changes are allocated the mappings files, I supposedly could make both versions of the level use separate ones.

    Does anyone know how to edit the levelartpointers to notice a different branch for a two player version of the same level?
     
  19. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,245
    1,415
    93
    your mom
    EDIT: See this instead. This post is missing some details.

    What you can do is in LevelArtPointers, after every zone entry, make a copy of it
    i.e.:
    Code (Text):
    1. LevelArtPointers:
    2.     levartptrs PLCID_Ehz1,     PLCID_Ehz2,      PalID_EHZ,  ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   0 ; EHZ  ; EMERALD HILL ZONE
    3.     levartptrs PLCID_Ehz1,     PLCID_Ehz2,      PalID_EHZ,  ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   0 ; EHZ  ; EMERALD HILL ZONE (2 PLAYER)
    4.     levartptrs PLCID_Miles1up, PLCID_MilesLife, PalID_EHZ2, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   1 ; LEV1 ; LEVEL 1 (UNUSED)
    5.     levartptrs PLCID_Miles1up, PLCID_MilesLife, PalID_EHZ2, ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   1 ; LEV1 ; LEVEL 1 (UNUSED, 2 PLAYER)
    6.     levartptrs PLCID_Tails1up, PLCID_TailsLife, PalID_WZ,   ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   2 ; LEV2 ; LEVEL 2 (UNUSED)
    7.     levartptrs PLCID_Tails1up, PLCID_TailsLife, PalID_WZ,   ArtKos_EHZ, BM16_EHZ, BM128_EHZ ;   2 ; LEV2 ; LEVEL 2 (UNUSED, 2 PLAYER)

    Then, replace the levartptrs macro above it with this:
    Code (Text):
    1. ; declare some global variables to be used by the levartptrs macro
    2. cur_zone_id := 0
    3. cur_zone_str := "0"
    4. cur_zone_2p := 0
    5.  
    6. ; macro for declaring a "main level load block" (MLLB)
    7. levartptrs macro plc1,plc2,palette,art,map16x16,map128x128
    8.     !org LevelArtPointers+zone_id_{cur_zone_str}*24+cur_zone_2p
    9.     dc.l (plc1<<24)|art
    10.     dc.l (plc2<<24)|map16x16
    11.     dc.l (palette<<24)|map128x128
    12. cur_zone_2p := cur_zone_2p+12
    13.     if cur_zone_2p>=24
    14. cur_zone_2p := 0
    15. cur_zone_id := cur_zone_id+1
    16. cur_zone_str := "\{cur_zone_id}"
    17.     endif
    18.     endm

    And have this added after the LevelArtPointers table:
    Code (Text):
    1.     if (cur_zone_2p<>0)&&(MOMPASS=1)
    2.     message "Warning: Table LevelArtPointers's last entry does not have a 2P entry"
    3.     endif

    Then, go to Level and go to the comment that says "; multiply d0 by 12, the size of a level art load block". Insert this before "lea (LevelArtPointers).l,a2":
    Code (Text):
    1.     add.w    d0,d0
    2.     tst.w    (Two_player_mode).w
    3.     beq.s    .not_2p_mode
    4.     addi.w    #12,d0
    5.  
    6. .not_2p_mode:

    Then go to both LoadZoneTiles and loadZoneBlockMaps and add the same code before both instances of "lea (LevelArtPointers).l,a2".

    With that, you should now be able to set up level data pointers for 2P mode for any zone.


    As an added bonus for applying the same thing to collision and level/object layouts...

    With that, you go to LoadCollisionIndexes and change "lsl.w #2,d0" into:
    Code (Text):
    1.     lsl.w    #3,d0
    2.     tst.w    (Two_player_mode).w
    3.     beq.s    .not_2p_mode
    4.     addq.w    #4,d0
    5.  
    6. .not_2p_mode:

    And then apply that copying logic thing that was done LevelArtPointers in Off_ColP and Off_ColS, and also for both tables, change "zoneOrderedTable 4,1" into "zoneOrderedTable 4,2".

    You may get an error regading "movea.l Off_ColS(pc,d0.w),a0" because Off_ColS is too far away for that instruction to work. Just change that to:
    Code (Text):
    1.     lea    Off_ColS(pc),a0
    2.     movea.l    (a0,d0.w),a0

    And it'll be good.

    Then, g to Off_Level, change "zoneOrderedOffsetTable 2,2" into "zoneOrderedOffsetTable 2,4", and do the same copying logic as before. Then go to loadLevelLayout, and change "lsr.w #6,d0" into:
    Code (Text):
    1.     lsr.w    #5,d0
    2.     tst.w    (Two_player_mode).w
    3.     beq.s    .not_2p_mode
    4.     addq.w    #2,d0
    5.  
    6. .not_2p_mode:

    Then, go to Off_Objects, change "zoneOrderedOffsetTable 2,2" into "zoneOrderedOffsetTable 2,4", and do the same copying logic as before. Also, do not forget to change the CNZ 2P object layout pointers here into Objects_CNZ1_2P and Objects_CNZ2_2P.

    Then, go to ObjectsManager_Init and change:
    Code (Text):
    1.     lsr.w    #6,d0            ; and this yields $003E.
    2.     lea    (Off_Objects).l,a0    ; Next, we load the first pointer in the object layout list pointer index,
    3.     movea.l    a0,a1            ; then copy it for quicker use later.
    4.     adda.w    (a0,d0.w),a0        ; (Point1 * 2) + $003E
    5.     tst.w    (Two_player_mode).w    ; skip if not in 2-player vs mode
    6.     beq.s    +
    7.     cmpi.b    #casino_night_zone,(Current_Zone).w    ; skip if not Casino Night Zone
    8.     bne.s    +
    9.     lea    (Objects_CNZ1_2P).l,a0    ; CNZ 1 2-player object layout
    10.     tst.b    (Current_Act).w        ; skip if not past act 1
    11.     beq.s    +
    12.     lea    (Objects_CNZ2_2P).l,a0    ; CNZ 2 2-player object layout
    13. +

    Into:
    Code (Text):
    1.     lsr.w    #5,d0            ; and this yields $003E.
    2.     tst.w    (Two_player_mode).w    ; skip if not in 2-player vs modew
    3.     beq.s    +
    4.     addq.w    #2,d0
    5. +
    6.     lea    (Off_Objects).l,a0    ; Next, we load the first pointer in the object layout list pointer index,
    7.     movea.l    a0,a1            ; then copy it for quicker use later.
    8.     adda.w    (a0,d0.w),a0        ; (Point1 * 2) + $003E

    And finally, move the CNZ 2P object layout data closer to the Off_Objects table.
     
    Last edited: Jul 7, 2020
  20. E-122-Psi

    E-122-Psi

    Member
    2,470
    612
    93
    Thanx for the quick reply. :D

    Just checking first. Is this for the GitHub or Xenowhirl version? I'm working on Xenowhirl.

    EDIT: Never mind. Just had to take out one line GitHub exclusive and it works.

    Thanx very much. :D
     
    Last edited: Jul 6, 2020