[-] An analysis of Schtserv's rare enemy rates

With schthack's relaunching of their blue burst server, I was curious if the infamous rare enemy reader prank addon worked on it. However, this took a strange turn into us analyzing the rates of rare enemies compared to other servers.

But first, lets go over how stock pso handles rare enemies. In memory there is a [u16; 16] where each element is an index into the object table, and 0xFFFF for a null value. For example if the first rag rappy were rare in the quest Respective Tomorrow the packet would look like this:
0000 | DE 00 18 00 00 00 00 00 1D 00 FF FF FF FF FF FF
0010 | FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0020 | FF FF FF FF FF FF FF FF
now removing the packet header and getting just the payload:
0000 | 1D 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0010 | FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF

This blob of data just gets directly copied into memory and is later referenced in a function:
// this function generated by ghidra's decompiler
bool __cdecl is_rare_enemy(short enemy_id)
{
  if ((rare_enemy_table != (short *)0x0) &&
     (((((enemy_id == *rare_enemy_table || (enemy_id == rare_enemy_table[1])) ||
        (enemy_id == rare_enemy_table[2])) ||
       (((enemy_id == rare_enemy_table[3] || (enemy_id == rare_enemy_table[4])) ||
        ((enemy_id == rare_enemy_table[5] ||
         ((enemy_id == rare_enemy_table[6] || (enemy_id == rare_enemy_table[7])))))))) ||
      ((enemy_id == rare_enemy_table[8] ||
       (((((enemy_id == rare_enemy_table[9] || (enemy_id == rare_enemy_table[10])) ||
          (enemy_id == rare_enemy_table[0xb])) ||
         ((enemy_id == rare_enemy_table[0xc] || (enemy_id == rare_enemy_table[0xd])))) ||
        ((enemy_id == rare_enemy_table[0xe] || (enemy_id == rare_enemy_table[0xf])))))))))) {
    return true;
  }
  return false;
}

The server would typically generate this data by going through each enemy, checking if it could be rare, rolling the die to see if it is rare, and if it is then add the enemy's id to the array.
Anyway, the packet schtserv sends out is quite strange:
0000 | 2D 01 A9 02 00 03 BC 04 9B 06 C8 07 B8 08 D6 09
0010 | 01 0B D6 0C 46 0E 17 10 88 10 93 12 84 13 F8 14
At this point we have myself, a Curious Bystander, an Anonymous Expert, and the Original Prankster all workshopping this trying to figure out what is going on. I immediately think this is all junk data and begin digging through schty.dll to see if any of these functions are getting monkeypatched. The Anonymous Expert, however, is certain the data is correct and the server is just doing something fucky.

Turns out he was right and I spent hours in schty.dll for nothing.

These values are properly used when the client checks for rare enemies, it just appears that they are generated in a strange way. And by strange I mean this is mostly junk, for example the largest quest in standard server rotation is Tyrell's Ego, which has 1054(0x41E) enemies. That number only encompasses the first three elements in this array. 0xB5O is the max amount of enemies a quest could possibly contain, which gets us all the way to the 9th element. the other 7 being completely useless.

Disclaimer: everything past this point is speculation on how schthack generates the ids for rare enemies based on observing the rare enemy packet that the server sends.

I speculate that the way these numbers are generated is that the server continually generates a number 0-511, adds it to the previous number, then puts it in the array until it is full.

So this feels wrong, but I can't quite place how. I'm sure theres some math-y way to prove it but I'm shit at math so here we are. So to prove to myself that the logic I assume the server is doing is worse I'm just gonna write a script that mocks out some runs of just temple in Respective Tomorrow looking at rappies.
from random import randrange
from functools import reduce
RAPPY_IDS =  {0x1D, 0x1E, 0x21, 0x22, 0x23, 0x6B, 0x6C, 0x46, 0x47, 0x48}
ITERATIONS = 1_000_000

def scht_rare_table():
    return set(reduce(lambda a,k: a + [k+a[-1]], [randrange(0, 512) for _ in range(15)], [randrange(0,512)]))

def vanilla_rare_table():
    return {rappy_id for rappy_id in RAPPY_IDS if randrange(0, 512) == 0}

def test_table(table_function):
    rare_counts = [len(table_function() & RAPPY_IDS) for _ in range(ITERATIONS)]
    return sum(rare_counts)
    
def report(id, count):
    print(f"{id}: {count/(len(RAPPY_IDS)*ITERATIONS)}")
    
report("scht", test_table(scht_rare_table))
report("vanilla", test_table(vanilla_rare_table))
print(f"ideal: {1/512.0}")
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.0022089
vanilla: 0.0019388
ideal: 0.001953125
Well thats interesting, if my assumption is correct then scht has slightly better rare enemy rates. Maybe this way has a better chance of getting multiple rare enemies in a single run?
from random import randrange
from functools import reduce
from collections import Counter
RAPPY_IDS = {0x1D, 0x1E, 0x21, 0x22, 0x23, 0x6B, 0x6C, 0x46, 0x47, 0x48}
ITERATIONS = 1_000_000

def scht_rare_table():
    return set(reduce(lambda a,k: a + [k+a[-1]], [randrange(0, 512) for _ in range(15)], [randrange(0,512)]))

def vanilla_rare_table():
    return {rappy_id for rappy_id in RAPPY_IDS if randrange(0, 512) == 0}

def test_table(table_function):
    rare_counts = [len(table_function() & RAPPY_IDS) for _ in range(ITERATIONS)]
    return (sum(rare_counts), Counter(rare_counts))
    
def report(id, count, counter):
    print(f"{id}: {count/(len(RAPPY_IDS)*ITERATIONS)} {counter}")
    
report("scht", *test_table(scht_rare_table))
report("vanilla", *test_table(vanilla_rare_table))
print(f"ideal: {1/512.0}")
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.0022244 Counter({0: 977939, 1: 21878, 2: 183})
vanilla: 0.0019589 Counter({0: 980597, 1: 19219, 2: 182, 3: 2})
ideal: 0.001953125
NOPE.

Disclaimer: from here on out I am really going to be hitting the limits of my math knowledge, anything I say here is possibly totally incorrect as I did not pay attention in school.

For science, what happens if instead RAPPY_IDS = set(range(10))?
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.0019614 Counter({0: 980556, 1: 19274, 2: 170})
vanilla: 0.00197 Counter({0: 980452, 1: 19397, 2: 150, 3: 1})
ideal: 0.001953125
WOW look at that almost perfect 1/512.

lets go through some different rappy id tables:
RAPPY_IDS = set(range(200, 210))
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.002896 Counter({0: 971291, 1: 28458, 2: 251})
vanilla: 0.0019588 Counter({0: 980588, 1: 19239, 2: 170, 3: 3})
ideal: 0.001953125
RAPPY_IDS = set(range(300, 310))
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.0035257 Counter({0: 965062, 1: 34621, 2: 315, 3: 2})
vanilla: 0.0019513 Counter({0: 980644, 1: 19200, 2: 155, 3: 1})
ideal: 0.001953125
RAPPY_IDS = set(range(400, 410))
jake@sharnoth ~/post/0006 $ python rarerates3.py
scht: 0.004307 Counter({0: 957297, 1: 42338, 2: 363, 3: 2})
vanilla: 0.0019622 Counter({0: 980550, 1: 19280, 2: 168, 3: 2})
ideal: 0.001953125
It seems that the higher the enemy id the more likely it is to be rare.

RAPPY_IDS = set(range(10, 0xb50, int(0xb50/10)))
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.0036391 Counter({0: 964174, 1: 35264, 2: 559, 3: 3})
vanilla: 0.0019534 Counter({0: 980630, 1: 19207, 2: 162, 3: 1})
ideal: 0.001953125
And an evenly distributed rappy id set is much higher of a rate than standard.

To control for maybe being incorrect about 512 being the number that the server adds on each interation, lets try with 1024 and see if the general properties of this remain the same.
from random import randrange
from functools import reduce
from collections import Counter
RAPPY_IDS = set(range(10, 0xb50, int(0xb50/10)))
ITERATIONS = 1_000_000

def scht_rare_table():
    return set(reduce(lambda a,k: a + [k+a[-1]], [randrange(0, 1024) for _ in range(15)], [randrange(0,1024)]))

def vanilla_rare_table():
    return {rappy_id for rappy_id in RAPPY_IDS if randrange(0, 512) == 0}

def test_table(table_function):
    rare_counts = [len(table_function() & RAPPY_IDS) for _ in range(ITERATIONS)]
    return (sum(rare_counts), Counter(rare_counts))
    
def report(id, count, counter):
    print(f"{id}: {count/(len(RAPPY_IDS)*ITERATIONS)} {counter}")
    
report("scht", *test_table(scht_rare_table))
report("vanilla", *test_table(vanilla_rare_table))
print(f"ideal: {1/512.0}")
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.0010403 Counter({0: 989644, 1: 10309, 2: 47})
vanilla: 0.0019608 Counter({0: 980565, 1: 19262, 2: 173})
ideal: 0.001953125
RAPPY_IDS = set(range(200, 210))
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.0011827 Counter({0: 988240, 1: 11693, 2: 67})
vanilla: 0.0019461 Counter({0: 980693, 1: 19154, 2: 152, 3: 1})
ideal: 0.001953125
RAPPY_IDS = set(range(300, 310))
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.0013261 Counter({0: 986798, 1: 13143, 2: 59})
vanilla: 0.0019439 Counter({0: 980747, 1: 19068, 2: 184, 3: 1})
ideal: 0.001953125
RAPPY_IDS = set(range(400, 410))
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.0014332 Counter({0: 985724, 1: 14220, 2: 56})
vanilla: 0.0019609 Counter({0: 980565, 1: 19263, 2: 170, 3: 2})
ideal: 0.001953125
RAPPY_IDS = set(range(10, 0xb50, int(0xb50/10)))
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.0017881 Counter({0: 982232, 1: 17655, 2: 113})
vanilla: 0.0019483 Counter({0: 980684, 1: 19149, 2: 167})
ideal: 0.001953125
So even if I the constant I have figured out is incorrect, this property should hold for whatever number they use.
As for why this is happening, I have no goddamn idea. Statistics classes are for sleeping, not listening. But I do suspect it has something to do with the fact that higher valued enemy ids can effectively have multiple rolls for a single enemy.

To control for variation with multiple enemy ids we will only have 1 id, and it will be the largest possible enemy id the game can handle.
RAPPY_IDS={0xB50}
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.003873 Counter({0: 996127, 1: 3873})
vanilla: 0.001917 Counter({0: 998083, 1: 1917})
ideal: 0.001953125
The result is approximately 1/256, twice as likely as a normal rare enemy.

However that will take minimum 6 rolls to reach, so to figure out the formula we should use something a bit lower, like 0xFF:
RAPPY_IDS={0xFF}
jake@sharnoth ~/post/0006 $ python rarerates.py
scht: 0.003202 Counter({0: 996798, 1: 3202})
vanilla: 0.001918 Counter({0: 998082, 1: 1918})
ideal: 0.001953125
So lets just take the first 4 rolls, for speed of computation.
# rolls# of possibilities that hit target
11
2256
332896
42829056
Where # of possiblility values come from
#include <stdio.h>

int rolls2(int target)
{
    int result = 0;
    for(int x = 0; x < 512; x++) {
        for(int y = 0; y < 512; y++) {
            if (x + y == target) {
                result++;
            }
        }
    }

    return result;
}

int rolls3(int target)
{
    int result = 0;
    for(int x = 0; x < 512; x++) {
        for(int y = 0; y < 512; y++) {
            for(int z = 0; z < 512; z++) {
                if (x + y + z == target) {
                    result++;
                }
            }
        }
    }

    return result;
}

int rolls4(int target)
{
    int result = 0;
    for(int w = 0; w < 512; w++) {
        for(int x = 0; x < 512; x++) {
            for(int y = 0; y < 512; y++) {
                for(int z = 0; z < 512; z++) {
                    if (w + x + y + z == target) {
                        result++;
                    }
                }
            }
        }
    }

    return result;
}

int main()
{
    int r2 = rolls2(0xFF);
    printf("r2: %d\n", r2);
    int r3 = rolls3(0xFF);
    printf("r3: %d\n", r3);
    int r4 = rolls4(0xFF);
    printf("r4: %d\n", r4);
}
So we have here 1/512 + 256/(512*512) + 32896/(512*512*512) + 2829056/(512*512*512*512) which equals 0.003215949982404709. Thats pretty close to the empirically measured value above.

With this, I am comfortable saying that Schtserv's rare enemy rates are probaly not what they intend, and given their new push towards a pure vanilla experience, they should fix this.


How this affects pranksnake kondreiu is left as an exercise for the reader.
[-] suffering with closures, references, and async

so I have a personal problem. a problem that can only be solved by passing functions into functions.
here I am going to document the iterations through which this code (d)evolved as code requirements elsewhere changed.
So the original problem is thus: I had some code that needed to coordinate two clients interacting with eachother. the primary structure was a HashMap.
the way in which I wanted to interact with this was that I would give it the `ClientId` of one client and it would then fetch for me the coresponding `other_client` and give me mutable references to both. and given the aforementioned personal problem, I figured I would solve this through passing a function to this structure:
use std::collections::HashMap;

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, ClientState>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, ClientState {
            other_client: client2,
            some_data: 0,
        });
        self.state.insert(client2, ClientState {
            other_client: client1,
            some_data: 0,
        });
    }

    pub fn with<T, F>(&mut self, client: ClientId, func: F) -> Result<T, StateError>
    where
        F: Fn(&mut ClientState, &mut ClientState) -> T
    {
        let c1 = self.state.get_mut(&client).ok_or(StateError::Blah)?;
        let c2 = self.state.get_mut(&c1.other_client).ok_or(StateError::Blah)?;

        Ok(func(c1, c2))
    }
}

fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |this, other| {
        this.some_data = 23;
        other.some_data = 5;
    }).unwrap();

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data);
}
cool great got the basic idea going lets see how it works
error[E0499]: cannot borrow `self.state` as mutable more than once at a time
  --> src/main.rs:37:18
   |
36 |         let c1 = self.state.get_mut(&client).ok_or(StateError::Blah)?;
   |                  --------------------------- first mutable borrow occurs here
37 |         let c2 = self.state.get_mut(&c1.other_client).ok_or(StateError::Blah)?;
   |                  ^^^^^^^^^^^^^^^^^^^----------------^
   |                  |                  |
   |                  |                  first borrow later used here
   |                  second mutable borrow occurs here
jokes on me of course it doesn't work those there consecutive `get_mut`s are just not gonna work luckily we have this rad `RefCell` thing to put in there to make this work:
use std::collections::HashMap;
use std::cell::RefCell;

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, RefCell<ClientState>>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, RefCell::new(ClientState {
            other_client: client2,
            some_data: 0,
        }));
        self.state.insert(client2, RefCell::new(ClientState {
            other_client: client1,
            some_data: 0,
        }));
    }

    pub fn with<T, F>(&mut self, client: ClientId, func: F) -> Result<T, StateError>
    where
        F: Fn(&mut ClientState, &mut ClientState) -> T
    {
        let mut c1 = self.state.get(&client).ok_or(StateError::Blah)?.borrow_mut();
        let mut c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.borrow_mut();

        Ok(func(&mut *c1, &mut *c2))
    }
}

fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |this, other| {
        this.some_data = 23;
        other.some_data = 5;
    }).unwrap();

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data);
}
$ cargo run
   Compiling sufferingwithwith v0.1.0 (/home/jake/projects/sharnoth.com/0005)
    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
     Running `target/debug/sufferingwithwith`
c1 23
c2 5
look at it go.


and this was great, perfect, ideal. until suddenly `F` needed to be async.
use std::collections::HashMap;
use std::cell::RefCell;

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, RefCell<ClientState>>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, RefCell::new(ClientState {
            other_client: client2,
            some_data: 0,
        }));
        self.state.insert(client2, RefCell::new(ClientState {
            other_client: client1,
            some_data: 0,
        }));
    }

    pub fn with<T, F>(&mut self, client: ClientId, func: F) -> Result<T, StateError>
    where
        F: Fn(&mut ClientState, &mut ClientState) -> T
    {
        let mut c1 = self.state.get(&client).ok_or(StateError::Blah)?.borrow_mut();
        let mut c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.borrow_mut();

        Ok(func(&mut *c1, &mut *c2))
    }
}

#[async_std::main]
async fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |this, other| async {
        this.some_data = 23;
        other.some_data = 5;
    }).unwrap().await;

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data);
}
error: lifetime may not live long enough
  --> src/main.rs:50:43
   |
50 |       state.with(ClientId(1), |this, other| async {
   |  ______________________________----_______-_^
   | |                              |          |
   | |                              |          return type of closure `impl Future` contains a lifetime `'2`
   | |                              has type `&'1 mut ClientState`
51 | |         this.some_data = 23;
52 | |         other.some_data = 5;
53 | |     }).unwrap().await;
   | |_____^ returning this value requires that `'1` must outlive `'2`

error: lifetime may not live long enough
  --> src/main.rs:50:43
   |
50 |       state.with(ClientId(1), |this, other| async {
   |  ____________________________________------_^
   | |                                    |    |
   | |                                    |    return type of closure `impl Future` contains a lifetime `'4`
   | |                                    has type `&'3 mut ClientState`
51 | |         this.some_data = 23;
52 | |         other.some_data = 5;
53 | |     }).unwrap().await;
   | |_____^ returning this value requires that `'3` must outlive `'4`
   |
   = note: requirement occurs because of a mutable reference to `u32`
   = note: mutable references are invariant over their type parameter
   = help: see  for more information about variance

error: could not compile `sufferingwithwith` due to 2 previous errors
and now the suffering begins

now this error totally makes sense. `state` is lending out `this` and `other` which get captured by this async block which may not be `.await`ed until after `state` stops existing.
lets throw some lifetimes in here and see if it helps:
use std::collections::HashMap;
use std::cell::RefCell;

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, RefCell<ClientState>>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, RefCell::new(ClientState {
            other_client: client2,
            some_data: 0,
        }));
        self.state.insert(client2, RefCell::new(ClientState {
            other_client: client1,
            some_data: 0,
        }));
    }

    pub fn with<'a, T, F>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError>
    where
        F: Fn(&'a mut ClientState, &'a mut ClientState) -> T
    {
        let mut c1 = self.state.get(&client).ok_or(StateError::Blah)?.borrow_mut();
        let mut c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.borrow_mut();

        Ok(func(&mut *c1, &mut *c2))
    }
}

#[async_std::main]
async fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |this, other| async {
        this.some_data = 23;
        other.some_data = 5;
    }).unwrap().await;

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data);
}
error[E0597]: `c1` does not live long enough
  --> src/main.rs:41:23
   |
34 |     pub fn with<'a, T, F>(&'a mut self, client: ClientId, func: F) -> Result
   |                 -- lifetime `'a` defined here
...
41 |         Ok(func(&mut *c1, &mut *c2))
   |            -----------^^-----------
   |            |          |
   |            |          borrowed value does not live long enough
   |            argument requires that `c1` is borrowed for `'a`
42 |     }
   |     - `c1` dropped here while still borrowed

error[E0597]: `c2` does not live long enough
  --> src/main.rs:41:33
   |
34 |     pub fn with<'a, T, F>(&'a mut self, client: ClientId, func: F) -> Result
   |                 -- lifetime `'a` defined here
...
41 |         Ok(func(&mut *c1, &mut *c2))
   |            ---------------------^^-
   |            |                    |
   |            |                    borrowed value does not live long enough
   |            argument requires that `c2` is borrowed for `'a`
42 |     }
   |     - `c2` dropped here while still borrowed
literally useless

throwing shit at the wall, maybe a second lifetime 'b that is less than 'a:
use std::collections::HashMap;
use std::cell::RefCell;

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, RefCell<ClientState>>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, RefCell::new(ClientState {
            other_client: client2,
            some_data: 0,
        }));
        self.state.insert(client2, RefCell::new(ClientState {
            other_client: client1,
            some_data: 0,
        }));
    }

    pub fn with<'a, 'b, T, F>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError>
    where
        'a: 'b,
        F: Fn(&'b mut ClientState, &'b mut ClientState) -> T
    {
        let mut c1 = self.state.get(&client).ok_or(StateError::Blah)?.borrow_mut();
        let mut c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.borrow_mut();

        Ok(func(&mut *c1, &mut *c2))
    }
}

#[async_std::main]
async fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |this, other| async {
        this.some_data = 23;
        other.some_data = 5;
    }).unwrap().await;

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data);
}
error[E0597]: `c1` does not live long enough
  --> src/main.rs:42:23
   |
34 |     pub fn with<'a, 'b, T, F>(&'a mut self, client: ClientId, func: F) -> Result
   |                     -- lifetime `'b` defined here
...
42 |         Ok(func(&mut *c1, &mut *c2))
   |            -----------^^-----------
   |            |          |
   |            |          borrowed value does not live long enough
   |            argument requires that `c1` is borrowed for `'b`
43 |     }
   |     - `c1` dropped here while still borrowed

error[E0597]: `c2` does not live long enough
  --> src/main.rs:42:33
   |
34 |     pub fn with<'a, 'b, T, F>(&'a mut self, client: ClientId, func: F) -> Result
   |                     -- lifetime `'b` defined here
...
42 |         Ok(func(&mut *c1, &mut *c2))
   |            ---------------------^^-
   |            |                    |
   |            |                    borrowed value does not live long enough
   |            argument requires that `c2` is borrowed for `'b`
43 |     }
   |     - `c2` dropped here while still borrowed

why did I think that would do anything.

its at this point that I notice all except one of my actual-code-that-caused-me-to-write-this `with` calls were using an async block internally. maybe I can just make `with` async and move on with my life
use std::collections::HashMap;
use std::cell::RefCell;
use std::future::Future;

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, RefCell<ClientState>>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, RefCell::new(ClientState {
            other_client: client2,
            some_data: 0,
        }));
        self.state.insert(client2, RefCell::new(ClientState {
            other_client: client1,
            some_data: 0,
        }));
    }

    pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError>
    where
        F: Fn(&'a mut ClientState, &'a mut ClientState) -> Fut,
        Fut: Future<Output=T>,
    {
        let mut c1 = self.state.get(&client).ok_or(StateError::Blah)?.borrow_mut();
        let mut c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.borrow_mut();

        Ok(func(&mut *c1, &mut *c2).await)
    }
}

#[async_std::main]
async fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |this, other| async {
        this.some_data = 23;
        other.some_data = 5;
    }).await.unwrap();

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data);
}
error[E0597]: `c1` does not live long enough
  --> src/main.rs:43:23
   |
35 |     pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result
   |                       -- lifetime `'a` defined here
...
43 |         Ok(func(&mut *c1, &mut *c2).await)
   |            -----------^^-----------
   |            |          |
   |            |          borrowed value does not live long enough
   |            argument requires that `c1` is borrowed for `'a`
44 |     }
   |     - `c1` dropped here while still borrowed

error[E0597]: `c2` does not live long enough
  --> src/main.rs:43:33
   |
35 |     pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result
   |                       -- lifetime `'a` defined here
...
43 |         Ok(func(&mut *c1, &mut *c2).await)
   |            ---------------------^^-
   |            |                    |
   |            |                    borrowed value does not live long enough
   |            argument requires that `c2` is borrowed for `'a`
44 |     }
   |     - `c2` dropped here while still borrowed
I suppose if I actually sat to think about this before just throwing code at my computer I would have realized this would never have solved anything. the result is still a future that needs to be `.await`ed outside of this function scope.

so how do I make my resulting borrowed clients live longer than this function? idk lets try Arc I'm getting desperate
use std::collections::HashMap;
use std::cell::RefCell;
use std::future::Future;

use async_std::sync::Arc;

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, Arc<RefCell<ClientState>>>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, Arc::new(RefCell::new(ClientState {
            other_client: client2,
            some_data: 0,
        })));
        self.state.insert(client2, Arc::new(RefCell::new(ClientState {
            other_client: client1,
            some_data: 0,
        })));
    }

    pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError>
    where
        F: Fn(&'a mut ClientState, &'a mut ClientState) -> Fut,
        Fut: Future<Output=T>,
    {
        let c1 = self.state.get(&client).ok_or(StateError::Blah)?.clone();
        let c2 = self.state.get(&c1.borrow().other_client).ok_or(StateError::Blah)?.clone();

        let mut c1b = c1.borrow_mut();
        let mut c2b = c2.borrow_mut();

        Ok(func(&mut *c1b, &mut *c2b).await)
    }
}

#[async_std::main]
async fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |this, other| async {
        this.some_data = 23;
        other.some_data = 5;
    }).await.unwrap();

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data);
}
error[E0597]: `c1` does not live long enough
  --> src/main.rs:45:23
   |
37 |     pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result
   |                       -- lifetime `'a` defined here
...
45 |         let mut c1b = c1.borrow_mut();
   |                       ^^^^^^^^^^^^^^^ borrowed value does not live long enough
...
48 |         Ok(func(&mut *c1b, &mut *c2b).await)
   |            -------------------------- argument requires that `c1` is borrowed for `'a`
49 |     }
   |     - `c1` dropped here while still borrowed

error[E0597]: `c2` does not live long enough
  --> src/main.rs:46:23
   |
37 |     pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result
   |                       -- lifetime `'a` defined here
...
46 |         let mut c2b = c2.borrow_mut();
   |                       ^^^^^^^^^^^^^^^ borrowed value does not live long enough
47 |
48 |         Ok(func(&mut *c1b, &mut *c2b).await)
   |            -------------------------- argument requires that `c2` is borrowed for `'a`
49 |     }
   |     - `c2` dropped here while still borrowed

error[E0597]: `c1b` does not live long enough
  --> src/main.rs:48:23
   |
37 |     pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result
   |                       -- lifetime `'a` defined here
...
48 |         Ok(func(&mut *c1b, &mut *c2b).await)
   |            -----------^^^------------
   |            |          |
   |            |          borrowed value does not live long enough
   |            argument requires that `c1b` is borrowed for `'a`
49 |     }
   |     - `c1b` dropped here while still borrowed

error[E0597]: `c2b` does not live long enough
  --> src/main.rs:48:34
   |
37 |     pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result
   |                       -- lifetime `'a` defined here
...
48 |         Ok(func(&mut *c1b, &mut *c2b).await)
   |            ----------------------^^^-
   |            |                     |
   |            |                     borrowed value does not live long enough
   |            argument requires that `c2b` is borrowed for `'a`
49 |     }
   |     - `c2b` dropped here while still borrowed
yeah the same problem the borrowing of the refcell does not last long enough for the actual running of the async function.

I'm starting to run out of ideas at this point.a Mutex would face similar problems.I suppose I could solve this problem by passing in the `RefCell` itself to the function but I would prefer to hide the details of the inner workings of with.
use std::collections::HashMap;
use std::cell::RefCell;
use std::future::Future;

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, RefCell<ClientState>>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, RefCell::new(ClientState {
            other_client: client2,
            some_data: 0,
        }));
        self.state.insert(client2, RefCell::new(ClientState {
            other_client: client1,
            some_data: 0,
        }));
    }

    pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError>
    where
        F: Fn(&'a RefCell<ClientState>, &'a RefCell<ClientState>) -> Fut,
        Fut: Future<Output=T>,
    {
        let c1 = self.state.get(&client).ok_or(StateError::Blah)?;
        let c2 = self.state.get(&c1.borrow().other_client).ok_or(StateError::Blah)?;

        Ok(func(c1, c2).await)
    }
}

#[async_std::main]
async fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |this, other| async {
        this.borrow_mut().some_data = 23;
        other.borrow_mut().some_data = 5;
    }).await.unwrap();

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data);
}
$ cargo run
   Compiling sufferingwithwith v0.1.0 (/home/jake/projects/sharnoth.com/0005)
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/sufferingwithwith`
c1 23
c2 5
yeah I really do not like the `borrow_mut`s in the function itself BUT HEY IT WORKS.

in a questionable state of mind I figure "hey what happens if I send the `RefMut`s just directly by value?"
use std::collections::HashMap;
use std::cell::{RefCell, RefMut};
use std::future::Future;

use async_std::sync::{Arc, Mutex};

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, RefCell<ClientState>>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, RefCell::new(ClientState {
            other_client: client2,
            some_data: 0,
        }));
        self.state.insert(client2, RefCell::new(ClientState {
            other_client: client1,
            some_data: 0,
        }));
    }

    pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError>
    where
        F: Fn(RefMut<ClientState>, RefMut<ClientState>) -> Fut,
        Fut: Future<Output=T>,
    {
        let c1 = self.state.get(&client).ok_or(StateError::Blah)?;
        let c2 = self.state.get(&c1.borrow().other_client).ok_or(StateError::Blah)?;

        let c1b = c1.borrow_mut();
        let c2b = c2.borrow_mut();

        Ok(func(c1b, c2b).await)
    }
}

#[async_std::main]
async fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |mut this, mut other| async move {
        this.some_data = 23;
        other.some_data = 5;
    }).await.unwrap();

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data);
}
error: lifetime may not live long enough
  --> src/main.rs:57:51
   |
57 |       state.with(ClientId(1), |mut this, mut other| async move {
   |  ______________________________--------___________-_^
   | |                              |                  |
   | |                              |                  return type of closure `impl Future` contains a lifetime `'2`
   | |                              has type `RefMut<'1, ClientState>`
58 | |         this.some_data = 23;
59 | |         other.some_data = 5;
60 | |     }).await.unwrap();
   | |_____^ returning this value requires that `'1` must outlive `'2`

error: lifetime may not live long enough
  --> src/main.rs:57:51
   |
57 |       state.with(ClientId(1), |mut this, mut other| async move {
   |  ________________________________________----------_^
   | |                                        |        |
   | |                                        |        return type of closure `impl Future` contains a lifetime `'4`
   | |                                        has type `RefMut<'3, ClientState>`
58 | |         this.some_data = 23;
59 | |         other.some_data = 5;
60 | |     }).await.unwrap();
   | |_____^ returning this value requires that `'3` must outlive `'4`
oh nevermind me just being dumb over here ignoring that naturally those structures would have lifetimes built into them.

anyway, it seems the core problems I am having is that references to outside data inside async closures is just not gonna work.so lets try some stupid shit and see which I hate least.
removing from hashmap to run the function then re-add after:
use std::collections::HashMap;
use std::cell::{RefCell, RefMut};
use std::future::Future;

use async_std::sync::{Arc, Mutex};

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, ClientState>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, ClientState {
            other_client: client2,
            some_data: 0,
        });
        self.state.insert(client2, ClientState {
            other_client: client1,
            some_data: 0,
        });
    }

    pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError>
    where
        F: Fn(&'a mut ClientState, &'a mut ClientState) -> Fut,
        Fut: Future<Output=T>,
    {
        let mut c1 = self.state.remove(&client).ok_or(StateError::Blah)?;
        let mut c2 = self.state.remove(&c1.other_client).ok_or(StateError::Blah)?;

        let result = func(&mut c1, &mut c2).await;

        let c1_client_id = c2.other_client;
        let c2_client_id = c1.other_client;
        self.state.insert(c1_client_id, c1);
        self.state.insert(c2_client_id, c2);

        Ok(result)
    }
}

#[async_std::main]
async fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |this, other| async move {
        this.some_data = 23;
        other.some_data = 5;
    }).await.unwrap();

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().some_data);
}
error[E0597]: `c1` does not live long enough
  --> src/main.rs:45:27
   |
37 |     pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result
   |                       -- lifetime `'a` defined here
...
45 |         let result = func(&mut c1, &mut c2).await;
   |                      -----^^^^^^^----------
   |                      |    |
   |                      |    borrowed value does not live long enough
   |                      argument requires that `c1` is borrowed for `'a`
...
53 |     }
   |     - `c1` dropped here while still borrowed

error[E0597]: `c2` does not live long enough
  --> src/main.rs:45:36
   |
37 |     pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result
   |                       -- lifetime `'a` defined here
...
45 |         let result = func(&mut c1, &mut c2).await;
   |                      --------------^^^^^^^-
   |                      |             |
   |                      |             borrowed value does not live long enough
   |                      argument requires that `c2` is borrowed for `'a`
...
53 |     }
   |     - `c2` dropped here while still borrowed

error[E0503]: cannot use `c2.other_client` because it was mutably borrowed
  --> src/main.rs:47:28
   |
37 |     pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result
   |                       -- lifetime `'a` defined here
...
45 |         let result = func(&mut c1, &mut c2).await;
   |                      ----------------------
   |                      |             |
   |                      |             borrow of `c2` occurs here
   |                      argument requires that `c2` is borrowed for `'a`
46 |
47 |         let c1_client_id = c2.other_client;
   |                            ^^^^^^^^^^^^^^^ use of borrowed `c2`

error[E0503]: cannot use `c1.other_client` because it was mutably borrowed
  --> src/main.rs:48:28
   |
37 |     pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result
   |                       -- lifetime `'a` defined here
...
45 |         let result = func(&mut c1, &mut c2).await;
   |                      ----------------------
   |                      |    |
   |                      |    borrow of `c1` occurs here
   |                      argument requires that `c1` is borrowed for `'a`
...
48 |         let c2_client_id = c1.other_client;
   |                            ^^^^^^^^^^^^^^^ use of borrowed `c1`

error[E0505]: cannot move out of `c1` because it is borrowed
  --> src/main.rs:49:41
   |
37 |     pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result
   |                       -- lifetime `'a` defined here
...
45 |         let result = func(&mut c1, &mut c2).await;
   |                      ----------------------
   |                      |    |
   |                      |    borrow of `c1` occurs here
   |                      argument requires that `c1` is borrowed for `'a`
...
49 |         self.state.insert(c1_client_id, c1);
   |                                         ^^ move out of `c1` occurs here

error[E0505]: cannot move out of `c2` because it is borrowed
  --> src/main.rs:50:41
   |
37 |     pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result
   |                       -- lifetime `'a` defined here
...
45 |         let result = func(&mut c1, &mut c2).await;
   |                      ----------------------
   |                      |             |
   |                      |             borrow of `c2` occurs here
   |                      argument requires that `c2` is borrowed for `'a`
...
50 |         self.state.insert(c2_client_id, c2);
   |                                         ^^ move out of `c2` occurs here

hahahah man jokes on me I have no idea what I am doing anymore.

ok so references here are infinite forever suffering how about I just pass the state by value and try and make that work in some capacity. so to pass by value then I need to like return the data back out:
use std::collections::HashMap;
use std::future::Future;

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

#[derive(Clone)]
struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, ClientState>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, ClientState {
            other_client: client2,
            some_data: 0,
        });
        self.state.insert(client2, ClientState {
            other_client: client1,
            some_data: 0,
        });
    }

    pub async fn with<T, F, Fut>(&mut self, client: ClientId, func: F) -> Result<T, StateError>
    where
        F: Fn(ClientState, ClientState) -> Fut,
        Fut: Future<Output=(ClientState, ClientState, T)>,
    {
        let c1 = self.state.get(&client).ok_or(StateError::Blah)?;
        let c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?;

        let (c1, c2, result) = func(c1.clone(), c2.clone()).await;

        let c1_client_id = c2.other_client;
        let c2_client_id = c1.other_client;
        self.state.insert(c1_client_id, c1);
        self.state.insert(c2_client_id, c2);

        Ok(result)
    }
}

#[async_std::main]
async fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |mut this, mut other| async move {
        this.some_data = 23;
        other.some_data = 5;

        (this, other, ())
    }).await.unwrap();

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().some_data);
}

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/sufferingwithwith`
c1 23
c2 5
I'm not sure if I dislike this more or less than the explicit borrow_mut()/lock() in the closure.I do think this version is susceptible to weird timing issues like the other one was.

so after some googling I find a possibly promising `FutureBoxLocal` future where I can specify a lifetime lets see how that goes
use std::collections::HashMap;
use std::cell::RefCell;

use futures::future::{Future, FutureExt, LocalBoxFuture};

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, RefCell<ClientState>>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, RefCell::new(ClientState {
            other_client: client2,
            some_data: 0,
        }));
        self.state.insert(client2, RefCell::new(ClientState {
            other_client: client1,
            some_data: 0,
        }));
    }

    pub fn with<'a, 'b, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<LocalBoxFuture<'a, T>, StateError>
    where
        'a: 'b,
        F: 'a + FnOnce(&'b mut ClientState, &'b mut ClientState) -> Fut,
        Fut: Future<Output=T> + 'b
    {
        let mut c1 = self.state.get(&client).ok_or(StateError::Blah)?.borrow_mut();
        let mut c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.borrow_mut();

        Ok(async move {
            func(&mut *c1, &mut *c2).await
        }
        .boxed_local())
    }
}

#[async_std::main]
async fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |this, other| async {
        this.some_data = 23;
        other.some_data = 5;
    }).unwrap().await;

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data);
}
error[E0597]: `c1` does not live long enough
  --> src/main.rs:46:24
   |
36 |     pub fn with<'a, 'b, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result, StateError>
   |                     -- lifetime `'b` defined here
...
46 |             func(&mut *c1, &mut *c2).await
   |             -----------^^-----------
   |             |          |
   |             |          borrowed value does not live long enough
   |             argument requires that `c1` is borrowed for `'b`
47 |         }
   |         - `c1` dropped here while still borrowed

error[E0597]: `c2` does not live long enough
  --> src/main.rs:46:34
   |
36 |     pub fn with<'a, 'b, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result, StateError>
   |                     -- lifetime `'b` defined here
...
46 |             func(&mut *c1, &mut *c2).await
   |             ---------------------^^-
   |             |                    |
   |             |                    borrowed value does not live long enough
   |             argument requires that `c2` is borrowed for `'b`
47 |         }
   |         - `c2` dropped here while still borrowed
nope that sure didn't help!

you know just for completions sake lets just do the MutexGuard version of that RefCellimplementation above oh wait look at those docs theres a lifetime parameter for MutexGuard<'a, T> (and apparently for RefCell which I totally did not notice which may have made my life easier a while ago). maybe that will save the day
use std::collections::HashMap;
use async_std::sync::{Mutex, MutexGuard};
use futures::future::Future;

#[derive(Copy, Clone, Hash, Eq, PartialEq)]
struct ClientId(u32);

struct ClientState {
    other_client: ClientId,
    some_data: u32,
}

#[derive(Debug)]
enum StateError {
    Blah,
}

#[derive(Default)]
struct State {
    state: HashMap<ClientId, Mutex<ClientState>>,
}

impl State {
    pub fn new_client(&mut self, client1: ClientId, client2: ClientId) {
        self.state.insert(client1, Mutex::new(ClientState {
            other_client: client2,
            some_data: 0,
        }));
        self.state.insert(client2, Mutex::new(ClientState {
            other_client: client1,
            some_data: 0,
        }));
    }

    pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError>
    where
        F: FnOnce(MutexGuard<'a, ClientState>, MutexGuard<'a, ClientState>) -> Fut + 'a,
        Fut: Future<Output=T>,
    {
        let c1 = self.state.get(&client).ok_or(StateError::Blah)?.lock().await;
        let c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.lock().await;

        Ok(func(c1, c2).await)
    }
}

#[async_std::main]
async fn main() {
    let mut state = State::default();
    state.new_client(ClientId(1), ClientId(2));

    state.with(ClientId(1), |mut this, mut other| Box::pin(async move {
        this.some_data = 23;
        other.some_data = 5;
    })).await.unwrap();

    println!("c1 {}", state.state.get(&ClientId(1)).unwrap().lock().await.some_data);
    println!("c2 {}", state.state.get(&ClientId(2)).unwrap().lock().await.some_data);
}
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/sufferingwithwith`
c1 23
c2 5
oh. huh. cool.
[-] On effort-free 4 way photon blasts in psogc

In PSO time attack there is a category for starting the quest with the highest level shifta/deband that could be achieved through a photon blast. In 4 player games this ends up being level 81. The problem is that it takes time to get this before each run.
So here I am trying to make it a bit more streamlined.
I'm specifically targetting psogc on actual hardware so things like trainers and memory editors you can use on blue burst (or psogc through dolphin) are not an option. That leaves trying to use a network proxy to spoof packets.

I`ll be using my own psogc proxy I`ve been working on: darkbridge
Idea: I can pretend someone else is casting level 81 sd on me.
First, how do techs work?
Casting foie 1 causes my client to send these 3 packets:
60 00 0C 00 8D 02 00 00 00 00 00 00
60 00 0C 00 47 02 00 00 00 00 00 00
60 00 0C 00 48 02 00 00 00 00 00 00
60 basically means echo it to all other people in the room, 0C is the length of the packet.
60 00 0C 00 8D 02 00 00 00 00 00 00
60 00 0C 00 47 02 00 00 00 00 00 00
60 00 0C 00 48 02 00 00 00 00 00 00
So these are the bytes we care about.
8D, 47, and 48 are subcommand identifers and 02 is the length (in u32s). The rest is the payload data except its all zeros and not helpful at all.
Lets try foie 2:
60 00 0C 00 8D 02 00 00 00 00 00 00
60 00 0C 00 47 02 00 00 00 00 01 00
60 00 0C 00 48 02 00 00 00 00 01 00
Found the tech level!
what happens if we cast shifta 1?
60 00 0C 00 8D 02 00 00 00 00 00 00
60 00 0C 00 47 02 00 00 0D 00 00 00
60 00 0C 00 48 02 00 00 0D 00 00 00
And shifta 30:
60 00 0C 00 8D 02 00 00 0F 00 00 00
60 00 10 00 47 03 00 00 0D 00 0E 01 01 00 00 00
60 00 0C 00 48 02 00 00 0D 00 0E 00
...wait what?
hex(30-1) = 1D, but in the level there is 0E and this new 0F in command 8D.
Well turns out 0E + 0F = 1D, but this doesn't explain why it got split up over two bytes.
And 47 has some additional data...
Shifta 1 only targets yourself, but shifta 30 can target other players. So this is just saying which other players it affected. The first 01 is the number of targets followed by a u32 specifying the client id of the target. So if you hit 3 other players with shifta the packet would be:
60 00 18 00 47 05 00 00 06 00 0E 03 01 00 00 00 02 00 00 00 03 00 00 00
But back to that splitting up of the tech level. What happens if we just max out the level to FF and send us that packet?
jake@sharnoth $ cat s30.txt
raw client 60 00 8D 02 01 00 FF 00 00 00
raw client 60 00 47 03 01 00 0D 00 FF 01 00 00 00 00
raw client 60 00 48 02 01 00 0D 00 FF 00
jake@sharnoth $ cat s30.txt > /tmp/darkbridge
This packet is crafted so it seems like the other player in the game is casting shifta on us. In red is the client id of the other player and in yellow is the client id of yourself.
Note we omit the length portion of the packet here as the command calculates it automatically.
On cast my atp goes from 616 to 916. The shifta buff algorithm is approximately (.0128 * (techlvl - 1) + 1.1) * base atp.
(.0128*(31 - 1) + 1.1)*616 = 914. so level 31 shifta?
Maybe its a signed int and it overflew, lets try 7F:
jake@sharnoth $ cat s30.txt
raw client 60 00 8D 02 01 00 7F 00 00 00
raw client 60 00 47 03 01 00 0D 00 7F 01 00 00 00 00
raw client 60 00 48 02 01 00 0D 00 7F 00
jake@sharnoth $ cat s30.txt > /tmp/darkbridge
Aaaaaaaaaand the shifta isn't even a high enough level to affect me? Maybe there is an upper cap of 81 shifta or something:
jake@sharnoth $ cat s30.txt
raw client 60 00 8D 02 01 00 28 00 00 00
raw client 60 00 47 03 01 00 0D 00 28 01 00 00 00 00
raw client 60 00 48 02 01 00 0D 00 28 00
jake@sharnoth $ cat s30.txt > /tmp/darkbridge
Same result, it is too low level to affect me.
what is going on? Okay, lets increment from values we know are correct 0F and 0E.
jake@sharnoth $ cat s30.txt
raw client 60 00 8D 02 01 00 0F 00 00 00
raw client 60 00 47 03 01 00 0D 00 0F 01 00 00 00 00
raw client 60 00 48 02 01 00 0D 00 0F 00
jake@sharnoth $ cat s30.txt > /tmp/darkbridge
616 -> 796: shifta level...16? (.0128*(16 - 1) + 1.1)*616 = 795.87. Does 0E + 1 overflow to 0?
lets increment the other variable:
jake@sharnoth $ cat s30.txt
raw client 60 00 8D 02 01 00 10 00 00 00
raw client 60 00 47 03 01 00 0D 00 0E 01 00 00 00 00
raw client 60 00 48 02 01 00 0D 00 0E 00
jake@sharnoth $ cat s30.txt > /tmp/darkbridge
616 -> 908, level 30. huh.
And if I just change the first one...?
jake@sharnoth $ cat s30.txt
raw client 60 00 8D 02 01 00 20 00 00 00
raw client 60 00 47 03 01 00 0D 00 0E 01 00 00 00 00
raw client 60 00 48 02 01 00 0D 00 0E 00
jake@sharnoth $ cat s30.txt > /tmp/darkbridge
30 again

It looks like the level of techs that can be cast through this method is capped to reasonable values.

In conclusion: techs are dumb
Full vod of my researching this at: https://www.youtube.com/watch?v=2P0RXrYhe3M except since I suck at streaming the audio of the person I was talking to during all of this isn't there so it looks like I'm just talking to myself like a lunatic
[-] New site! (again)

So in my infinite free time I figured I should actually do something with this site.
Maybe.
Well at the very least I`ve filled out the projects page and made it look a bit nicer.
P.S. I can no longer reach dh through ssh (rip)
[-] I have no idea what I am doing

Rewrote site already because I was bored and it was easier than actually putting content on here.
Also added some sort of forum to discuss whatever I end up using this site for (ha ha ha).