An effort to improve episode 6 support for private servers

@Bowie

C++:
CUser::AddApplySkillBuff(user, skill);

Will this sends Packet 0x50D on its own or do I have to do it by myself?

I tried this

C++:
auto charName = args.at(0);  // character name
    auto skillId = std::stoi(args.at(1));
    auto skillLevel = std::stoi(args.at(2));

    auto user = CWorld::FindUser(charName.c_str());
    if (!user) {
        std::stringstream output_format;
        output_format << "Character " << charName << " not found";
        output = output_format.str();
        return 0; // Exit if user is not found
    }

    auto skill = CGameData::GetSkillInfo(skillId, skillLevel);
    if (!skill) {
        std::stringstream output_format;
        output_format << "Skill with ID " << skillId << " and level " << skillLevel << " not found";
        output = output_format.str();
        return 0; // Exit if skill is not found
    }

    // Prepare the SkillUseOutgoing packet
    SkillUseOutgoing outgoing{};
    outgoing.senderId = user->connection.object.id;
    outgoing.targetId = user->connection.object.id;
    outgoing.skillId = skill->skillId;
    outgoing.skillLv = skill->skillLv;

    // Send the skill usage packet to the user
    Helpers::Send(user, &outgoing, sizeof(SkillUseOutgoing));

    // Apply the skill buff
    CUser::AddApplySkillBuff(user, skill);

    EnterCriticalSection(&user->applySkills.cs);

    // Start from the first node after the sentinel tail
    auto node = user->applySkills.sentinel.tail;
    node = node->next;  // Move to the first node
    user->applySkills.sentinel.head = node;

    int index = 0; // Index to track the skill position in the list
    bool skillFound = false;

    while (node && node != user->applySkills.sentinel.tail)
    {
        auto skillIn = reinterpret_cast<CSkill*>(node);

        // Check if the skill matches
        if (skillIn->skillId == skill->skillId && skillIn->skillLv == skill->skillLv)
        {
            skillFound = true;

            // Correctly set the id for addBuff
            SkillApplyOutgoing addBuff{};
            addBuff.id = index; // Send the matching skill's index
            addBuff.skillId = skill->skillId;
            addBuff.skillLv = skill->skillLv;

            Helpers::Send(user, &addBuff, sizeof(SkillApplyOutgoing));
            break;
        }

        node = node->next;  // Move to the next node
        user->applySkills.sentinel.head = node;
        index++; // Increment index
    }

    LeaveCriticalSection(&user->applySkills.cs);

    if (!skillFound)
    {
        std::stringstream output_format;
        output_format << "Skill " << skillId << " (Level " << skillLevel << ") could not be applied for " << charName;
        output = output_format.str();
        return 0; // Exit after reporting error
    }

    // Output a success message
    std::stringstream output_format;
    output_format << "Skill " << skillId << " (Level " << skillLevel << ") triggered for " << charName;
    output = output_format.str();
    return 0;

Upon testing, it says Example

Code:
Skill 287 (Level 1) could not be applied for Character
 
Will this sends Packet 0x50D on its own or do I have to do it by myself?
I don't see it sending that packet.

I think you should use "nostrum" skills and do it this way:

C++:
auto charName = args.at(0);  // character name
auto skillId = std::stoi(args.at(1));
auto skillLevel = std::stoi(args.at(2));

auto user = CWorld::FindUser(charName.c_str());
if (!user) {
    std::stringstream output_format;
    output_format << "Character " << charName << " not found";
    output = output_format.str();
    return 0; // Exit if user is not found
}

auto skill = CGameData::GetSkillInfo(skillId, skillLevel);
if (!skill) {
    std::stringstream output_format;
    output_format << "Skill with ID " << skillId << " and level " << skillLevel << " not found";
    output = output_format.str();
    return 0; // Exit if skill is not found
}

if (!CUser::UseItemSkill(user, skill)) {
    std::stringstream output_format;
    output_format << "Skill " << skillId << " (Level " << skillLevel << ") could not be applied for " << charName;
    output = output_format.str();
    return 0; // Exit after reporting error
}

// Output a success message
std::stringstream output_format;
output_format << "Skill " << skillId << " (Level " << skillLevel << ") triggered for " << charName;
output = output_format.str();
return 0;

I had to update the return type of UseItemSkill, so please change it from void to bool in your code.
 
Last edited:
1734086358554.png


I want to modify the code of this in the server side. Anyone knows the address to get started (in Essentail files)
 
@Zhein

If you want to overwrite the case and write your own handler, you could set a detour at 0x47A010, with 8 bytes as the length and jmp to 0x47A018 at the end of the assembly function. The user is in edi and the packet is in esi.

Capture.PNG


C++:
void item_remake_handler(CUser* user, ItemRemakeIncoming* incoming)
{
}


Code:
pushad

push esi // packet
push edi // user
call item_remake_handler
add esp,0x8

popad

jmp u0x47A018
 
Last edited:
@nurum

Here's the source that was in the repo before I removed it.
 

Attachments

  • character_effect_01.zip
    2 KB · Views: 1
@Bowie, do you have discord?

C++:
void item_remake_handler(CUser* user, ItemRemakeIncoming* incoming) {


    // Check items in the specified slots
    auto* item1 = user->inventory[incoming->bag1][incoming->slot1];
    auto* item2 = user->inventory[incoming->bag2][incoming->slot2];
    auto* item3 = user->inventory[incoming->bag3][incoming->slot3];


    // Ensure all items are present
    if (!item1 || !item2 || !item3) {
        Helpers::SendNotice("LOG: One or more items are missing.");
        return;
    }


    // Ensure all items have the same ID
    if (item1->itemInfo->itemId != item2->itemInfo->itemId ||
        item1->itemInfo->itemId != item3->itemInfo->itemId) {
        Helpers::SendNotice("LOG: Items do not match.");
        return;
    }


    // Handle specific item creation
    ItemInfo* resultItem = nullptr;
    if (item1->itemInfo->itemId == 72001) {
        int resultType = 72;
        int resultTypeId = 2;
        resultItem = CGameData::GetItemInfo(resultType, resultTypeId);
        if (resultItem) {
            std::string resultLog = "LOG: Created result item with Type: " +
            std::to_string(resultType) + ", TypeID: " + std::to_string(resultTypeId);
            Helpers::SendNotice(resultLog.c_str());
        }
        else {
            Helpers::SendNotice("LOG: Failed to create result item.");
            return;
        }
    }
    else {
        Helpers::SendNotice("LOG: Item ID does not match special handling conditions.");


        ItemRemakeOutgoing outgoing{};
        outgoing.result = ItemRemakeResult::Failure;
        Helpers::Send(user, &outgoing, sizeof(ItemRemakeOutgoing));


        return;
    }


    
    uint8_t bag = 1; // Result item bag
    uint8_t slot = -1; // Result item slot
    while (bag <= user->bagsUnlocked) {
        slot = Helpers::GetFreeItemSlot(user, bag);
        if (slot != -1) {
            if (CUser::ItemCreate(user, resultItem, 1)) {
                break;
            }
        }
        ++bag;
    }


    // Validate the resulting item placement
    auto* findResult = user->inventory[bag][slot];
    if (!findResult) {
        Helpers::SendNotice("LOG: Result item was not created.");
        return;
    }


    // Transfer the details of the first item to the result
    findResult->gems = item1->gems;
    findResult->craftName = item1->craftName;
    findResult->quality = item1->quality;


    // Assign the findResult back to the inventory
    user->inventory[bag][slot] = findResult;


    //Log the inventory[bag][slot] if already have its gems, craftname and quality
    Helpers::ItemRemove(user, item1->itemInfo->itemId, 1);
    Helpers::ItemRemove(user, item2->itemInfo->itemId, 1);
    Helpers::ItemRemove(user, item3->itemInfo->itemId, 1);


    // Send outgoing packet with result details
    ItemRemakeOutgoing outgoing{};
    outgoing.result = ItemRemakeResult::Success;
    outgoing.bag = bag;
    outgoing.slot = slot;
    outgoing.type = resultItem->type;
    outgoing.typeId = resultItem->typeId;
    outgoing.gems = findResult->gems;
    outgoing.count = findResult->count;
    outgoing.craftName = findResult->craftName;
    Helpers::Send(user, &outgoing, sizeof(ItemRemakeOutgoing));


    GameLogItemRemakeIncoming remakeLog(user, findResult, item1->uniqueId, item2->uniqueId, item3->uniqueId);
    Helpers::SendGameLog(&remakeLog, sizeof(GameLogItemRemakeIncoming));


    Helpers::SendNotice("LOG: Handler completed successfully.");


}

Can I ask why the item gems, craftname and quality is not persistent?
 
Can I ask why the item gems, craftname and quality is not persistent?

You have to send a packet to the dbAgent service. Looks like the remake function sends 0x701. I added it to the library.

Please add range checks for the bags and slots. :p

Edit: sending 0x711 and 0x717 to the dbAgent service resolved the issue.
 
Last edited:
[ENABLE]
alloc(newmem,256)

newmem:

slot14:
cmp byte ptr [esi+24],#91
je 0046846B
jmp fail

slot15:
cmp byte ptr [esi+24],#92
je 0046846B
jmp fail

jumps:
dd 004683C2
dd 004683D3
dd 004683E4
dd 004683F1

dd 004683FE
dd 00468395
dd 0046840B
dd 0046842D

dd 0046843A
dd 00468447
dd 00468447
dd 00468454

dd 00468454
dd 00468461
dd slot14 // 18
dd slot15 // 19

dd fail
dd fail
dd fail
dd 004684C6

fail:
xor al,al
pop esi
ret

0046838E:
jmp dword ptr [eax*4+jumps]

00468387:
db #15

0046862D:
db #16

00468955:
db #16

004685E6:
cmp ebx,0D
jnl 00468610

004685FD:
cmp eax,0D
jnl 0046861E

[DISABLE]

0046838E:
jmp dword ptr [eax*4+0046853C]

00468387:
db 0D

0046862D:
db 0E

00468955:
db 0E

004685E6:
cmp ebx,0D
jnl 00468610

004685FD:
cmp eax,0D
jnl 0046861E

dealloc(newmem)

Can someone help me change the address above the wings
 
Last edited:
Back
Top