An Ashita v4 addon that bridges two FFXI linkshells ↔ Discord, two ways, using simple file-based IPC.
Built for HorizonXI. Pairs with the ffxi-jarvis Discord bot, which polls/writes the same files and relays each linkshell to its own Discord channel.
FFXI (Ashita) --append--> ffxi_to_discord.txt --poll--> Discord bot --> #ls1 / #ls2
^ |
| <--/l or /l2 [Discord]-- discord_to_ffxi.txt <--append-- Discord bot <--
- The addon listens to
text_in, tags each linkshell message with its source (LS1orLS2) and writes it toffxi_to_discord.txtasLS1|name|message. - It polls
discord_to_ffxi.txt(linesLS1|user|message) and re-broadcasts new Discord messages into the matching linkshell via/l(LS1) or/l2(LS2), prefixed with[Discord]. - Messages containing
[Discord]are skipped on the FFXI side to prevent loops.
- Copy the
lsbridgefolder into your Ashitaaddonsdirectory, e.g....\HorizonXI\Game\addons\lsbridge\lsbridge.lua - Edit
DATA_DIRnear the top oflsbridge.luato point at the shared IPC folder used by the ffxi-jarvis bot (<bot>\data). - In the bot, set
BRIDGE_CHANNEL_ID(LS1) and optionallyBRIDGE_CHANNEL_ID_2(LS2) in.env. - In game:
/addon load lsbridge
/lsbridge [subcommand]
| Subcommand | Description |
|---|---|
status |
Show enabled state, per-LS on/off, modes, poll interval |
on / off |
Enable / disable the whole bridge |
ls1 |
Toggle LS1 bridging on/off (chat modes 6 = self, 14 = others) |
ls2 |
Toggle LS2 bridging on/off (chat modes 27 = self, 15 = others, assumed) |
test [ls2] |
Write a test line to the Discord file (LS1, or LS2 if ls2 given) |
say |
Toggle how Discord messages appear in game: local native-looking lines (only you) vs broadcast to the whole LS via /l |
window |
Toggle the ImGui Discord chat window |
clear |
Clear both IPC files |
clearchat |
Clear the Discord chat window history |
debug |
Toggle printing every text_in mode to the console |
logmode |
Toggle logging every text_in mode to modes_debug.txt |
pktscan |
Toggle a summary scan of incoming packet ids (find the online-members packet — see below) |
pktdump <0xID|all|names|group|off> |
Hex+ASCII dump packets to packets_debug.txt: a single id, all (everything except position/entity noise), names (only name-bearing packets, skipping known noise), group (only the party/group/linkshell-structure packets — best for finding the roster), or off |
The rich in-game Linkshell window (every online member with their main job and current zone) is a HorizonXI custom feature — retail FFXI has no packet that lists the whole online roster, and there's no published packet id for Horizon's version. So the addon can't read it out of the box; the packet has to be identified live, then parsed.
The addon ships read-only diagnostics to do that (they never block or modify packets, and are off by default).
What we've ruled out so far:
- Opening the Linkshell window sends no packet (the client already has the data cached).
- Zoning doesn't deliver a roster.
- The Ashita v4 SDK exposes no linkshell roster in memory — its memory manager only
offers party/alliance (up to 18 members), entities, player, target and inventory. (An
entity-table scan matching your own
LinkshellColorcan find LS members in your current zone, but not those elsewhere.) - No installed HorizonXI addon (HXUI, xiui, etc.) implements the window — it's compiled into the custom client, fed by a packet no addon handles.
- Earlier "name" leads were red herrings:
0x070is a crafting result (a nearby player's synthesis), and0x041is the blacklist packet — neither is linkshell data.
Leading theory (from packet reverse-engineering docs): retail's group-list packets
0x0DD (GP_SERV_GROUP_LIST) and 0x0E2 (GROUP_LIST2) carry, per member, a name
(offset 0x28), zone (0x20), main job/level (0x22/0x23) and a "Kind" byte at 0x1C.
Kind == 0 is your party/alliance; a non-zero Kind is the strongest suspect for how
HorizonXI pushes the online linkshell roster (reusing the existing client handler). Those
packets arrive at login.
Recommended workflow — group mode across a relog:
/lsbridge pktdump group— dumps only the party/group/linkshell family (0x0C8/0x0DD/0x0DF/0x0E0/0x0E1/0x0E2).- Log out to character select and back in with your LS pearl equipped (do this while LS members who are not in your party are online), then play briefly.
/lsbridge pktdump off— stop.- In
packets_debug.txt, look at each0x0DD/0x0E2entry's byte0x1C(Kind): any entry with a non-zero Kind is an online member that isn't your party — i.e. the roster. Its name (0x28), zone (0x20) and job (0x22) decode the rest.
Other tools:
/lsbridge pktdump names— dump only packets with player-name-like text (skips the party family, so it will not catch a0x0DD-based roster; usegroupfor that)./lsbridge pktdump all— dump every packet except high-volume noise (full login/zone burst)./lsbridge pktscan(start → relog → stop) — lists which packet ids appear; watch for any id above0x11E, which would be a genuinely custom HorizonXI packet./lsbridge pktdump 0xNNN— dump a single suspected id.
Once the id and field offsets are known, that packet can be parsed into a table and shown in an on-screen list (and optionally pushed to Discord). Note HorizonXI's addon policy requires custom addons to be published/approved for general use.
Your own and other players' linkshell messages arrive on different text_in mode numbers:
- LS1:
6= your own messages,14= everyone else.
If messages from others don't relay on your server, run /lsbridge logmode, have someone talk in
LS, and check modes_debug.txt to find the correct mode numbers.
MIT