Results 1 to 12 of 12
  1. #1
    VXP's Avatar
    Join Date
    Apr 2012
    Gender
    male
    Posts
    17
    Reputation
    18
    Thanks
    4
    My Mood
    Drunk

    Wink Official King of Kings III Reverse Engineering Thread

    Welcome!

    To my official King of Kings III reverse engineering thread!



    So a little backstory to this project. A long time ago, in a virtual galaxy far, far away, an MMORPG was released, developed by Lager Interactive (Thai) and published by GamigoAG.
    When the game first released, the level cap was 50. When I started playing, the cap was 140. In the end, the game died with a cap of 215, with a rumored Thai only version cap of 260 including more items, gear, and zones to explore.
    Of course, as with any game, I wanted to hack it or cheat in some way. I was never successful while the game was still available.
    However, I have a copy of the most recently updated game files, luckily I still had a copy on an old hard drive. I even have a copy of the 140 cap version of the game! When I first started playing, the community was amazing, staff were amazing, and even though the visual aesthetics weren't flashy-flashy, it was the community and content that made it one of a kind. As the years went by, staff began to neglect the game, the developers over-developed content and updates, and eventually the community started to evaporate, leaving behind only the most dedicated and hardcore fans. I was one of them. As the final month of the game's life approached, the publisher decided to have a "farewell" party for all the remaining players. Free in-game currency, massive level boosts and plentiful events made for one hell of a time, it also solidified a very fond spot in my heart as I was ultimately forced to accept the game's death as I was disconnected from the server due to it being shut down. They warned us with an in-game message box, a chilling message, "Server shutdown in 1 hour", and man did I party hard till the end. We all did. To be honest, not even breaking up with a girl hurt my heart as much as the "Failed to connect to server" message did when I tried to log in again. Ever since then, I swore I would bring the game back to life, however difficult.

    So here we are. Right here. Right now (and however long it takes ).
    I will make this known right now: I'm very determined. I have a decent knowledge of C++ and currently I'm looking in to DirectX development. I want this revamped version to support DX9, 10, and 11, along with OpenGL.

    Things to note:
    • I have access to all the game content. I've been working at this project for 2 weeks now as of this post and I have made a decent amount of progress. My biggest issues are dissecting the data files and writing the game engine.
    • I have all the time in the world, if anyone here would like to help me in this project, it would be much appreciated. I do not know how to program a game server, however, as with learning to develop with DirectX, YouTube and Google are my best friends and I'm not afraid to search thoroughly before I ask.
    • I do NOT want to make money off of this project. I want to develop a main server for everyone to connect to, along with a secondary, special "private" server application that allows people to download their character from the main server.
    • I also want to develop a single-player mode with a different story line and Easy/Medium/Hard difficulties.



    Section for Updates:

    May 22 2018 - Including all work done previously:
    • Identified file: (.dfm) "Form" file, unknown format, contains mixture of XML/HTML like structures
    • Identified file: (.nut) "Squirrel" script file, C++ like syntax, found packed in LPQ files.
    • Resources: 1 (.swf) Shockwave Flash file, I've never seen it in-game or during game operation, many (.LPQ) files (seem to be related to LagerPacketOMF.dll), many (.bhl) files (seem to relate directly to LPQ files)
    • LPQ files seem to be packed data files, unknown format, in many places the data remains uncompressed and is still readable, Squirrel script can be identified in some places along with XML/HTML-like markup.
    • If you corrupt an LPQ file and force-load WE.exe, it creates a crash report dump which reveals file names that could not be loaded. I am still working on creating dumps.
    • I have a memory dump of WE.exe where various in-game text, objects, scripts and other data can be identified
    • The string table that Cheat Engine creates of WE.exe reveals bits of strings that can be identified within LPQ files.
    • When loading WEbug.exe in IDApro and running in a disassembler, various (.nut) files can be identified by name. WEbug.exe is also the debug form of the main WE.exe
    • Within the LPQ files, if you look at the data it makes patterns within each line. Various string similarities can be seen such as:
    • WlT-µ’!
    • blTµ’!
    • UlT/µ’!
    • PlT*µ’! -- Without any data context, it's clear that these 4 distinct lines begin with similar string content (mind you there is much more to each line, those are just the first 7 characters of each line)
    • I'm confident in my ability to decode the strings manually, however help would be awesome considering all the LPQ's add up to over 1GB
    • There is also an internal "file system" so to speak, with folder roots and branches, which all have respective data, such as LIGHTS\, INTERFACE\, etc as root folders. I'm not fluent enough to understand how one might go about determining how to identify these paths.


    May 23 2018
    • Upon using PE Explorer and opening Login.exe (game client update application), I found a section identifying (.dfm) script. It seems to be line-based script, images within a (.dfm) are encoded as long strings of numbers. It's not base64 code, but I can't identify it yet. PE Explorer can view them fine and they're easily saved.
    • String map of WE.exe while loaded is very useful, however, it doesn't help to have a file name without file data or knowing where I might find the data.

    --- End Section ---

    *** THIS IS A C++ PROJECT ***

    I will be glad to share game data files/custom source files for research over some platform like Steam (preferred). PM me for my information.

    Well, I hope this project gets rolling as quick as possible. I'm utterly DYING with heartache to play this game again and I'd love to make anyone that helps part of the admin team
    Thank you for reading my project!

    *** I JUST FOUND A CHINESE WEBSITE HOSTING THE GAME FILES + SERVER + OPEN REGISTRATION ***
    *** IM SO HAPPY RIGHT NOW YOU GUYS DON'T EVEN UNDERSTAND ***

    Last edited by VXP; 05-23-2018 at 05:11 PM. Reason: Update

  2. The Following 2 Users Say Thank You to VXP For This Useful Post:

    Dama (07-01-2018),[MPGH]Hector (06-04-2020)

  3. #2
    VXP's Avatar
    Join Date
    Apr 2012
    Gender
    male
    Posts
    17
    Reputation
    18
    Thanks
    4
    My Mood
    Drunk
    MAJOR BREAKTHROUGH!

    I will now be able to show some significant progress.

    LPQ files have been interpreted up to 50%, that is to say, I understand how they're structured, and how the game loads pieces of every one.

    LPQ Structure:
    LPQ files do not seem to be compressed, more like they are simple files that store data files, and have the data inside encrypted.

    Header (4bytes) 0x0+4, Magic Number (4bytes) 0x4+4 (total 8 bytes)
    Code:
    4C 50 51 1A 20 00 00 00
    ALL LPQ's MUST have 0x4+4 as
    Code:
    20 00 00 00
    Various "End of Data"-like tags

    DFM script file header:
    Code:
    {object
    DFM script first line mandatory:
    Code:
    {object "<DFM form name>" : GUId,
    All files inside LPQ file structure have unique 4 byte endings post file. Maybe file separator or key for decryption.

    config.lpq corrosponds to config\ directory.

    Unique findings:
    If the game locates a required file on disk, it will load the disk version instead of the LPQ version, and the more important finding is that when reading from disk based content, files must be in original format (ex jpeg must remain in jpeg format, not LPQ encrypted internal data).

    Currently have changed loading screen, and main login screen.

    Currently: Analyzing more DFM scripts in encrypted format to determine range of data and possibly chunk frequencies.

    Tested using non-LPQ custom written resource FormInput.dfm in Interface\VerENG_DFM\ with
    Code:
    {object "FormInput" : GUId}
    with no exception error thrown.

    If DFM files do not follow a certain format or syntax, you will get a game crash with log.

    - - - Updated - - -

    Code:
    // Currently identified file structures:
    \{l}\ //Unknown exactly
    \{l}\win_b64\
    \{l}\win_b64\code\
    \{l}\win_b64\code\bin\
    \{l}\win_b64\code\bin\sginfra.dll
    \config\ //Configuration scripts/data
    \config\chainefftbl.txt
    \config\MountBornEffect.txt
    \config\Tahoma //Unknown unique file
    \config\WebLink.txt
    \config\WebLinkConfig.txt
    \ErrorReport\ //Game engine memory dump + debug output txt dump
    \gkk2.tmp\ //Unknown game engine temp folder, btw see Sec. 2
    \Help\ //Game help dir (web browser based help, also entirely in chinese)
    \Help\index.htm
    \Help\main.htm
    \Help\menu.htm
    \Help\Images // Too many files to list
    \Interface\
    \Interface\gcGeneral\
    \Interface\VerENG_DFM\ //Too many files to list
    \Lights\
    \map\
    \map\50_2.dds
    \Mesh\
    \Mesh\Article\
    \Mesh\Building\
    \Mesh\Nature\
    \Mesh\Treasure\ //Too many files to list
    \Photo\ //Game screenshot save dir
    \Shore\
    
    + various root files
    Section 2:
    I found the very first game's installation files. It seems to be the GKK engine (G King of Kings). G is unidentified at this time.
    King of Kings 3 uses King of Kings 2's game engine, called GKK2.
    GKK and GKK2 work entirely different. GKK has all resources as resources in DLL files, where GKK2 all resources can be either referenced through their true directory location, or in the LPQ library. GKK2 will prioritize loading of true location files before the LPQ library. This allows us to create custom files and view their effect on the game.

  4. The Following User Says Thank You to VXP For This Useful Post:

    Dama (07-01-2018)

  5. #3
    VXP's Avatar
    Join Date
    Apr 2012
    Gender
    male
    Posts
    17
    Reputation
    18
    Thanks
    4
    My Mood
    Drunk
    Update 8/24/2018

    I have successfully infiltrated the game's engine. Here's a raw breakdown of what's going on:

    Game uses early 2000's level of sophistication, however they included the debug executable. Anyone remember Dead Island's issues? So what this means for us is they screwed up. Big.

    WE.exe file size: ~6MB
    WEbug.exe file size: ~14MB [8MB+ of debug information, bigger than the main executable itself!]

    So now in correlation to my previous post, -I have successfully grabbed the engine by the balls here-, and executed the reverse.

    Let me introduce you to Squirrel script injection!
    Squirrel syntax/dictionary found here: https://www.squirrel-lang.org/
    Squirrel Wiki: https://en.wikipedia.org/wiki/Squirr...ming_language)
    The game will look for content on disk first. If there is no content, it will look for the exact immediate file necessary in the local LPQ. However, if the file IS present, and the file is any file of the required type and in proper format, so mp3 must be a valid mp3 file, tga/bmp/tiff/png/dds, and of course, DFM forms and Squirrel NUT scripts - which just so happen to be plain text, hooray -, it will use it and load it.

    So, now that we can do script, music, image, form, light, mesh and text injection, we can start doing whatever we want.
    I have identified a very important instruction too, SqRunFile(string);

    By looking at configupdate.lpq you can find ScriptInit.nut at offset 15,735,785. File size in LPQ is 812 bytes.

    I've done a test, I created a ScriptInit.nut in my game's config\ folder and gave it these lines:

    ScriptInit.nut
    Code:
    // This is a comment, fuck the developer fuckers
    // Welcome to the jungle
    
    SqRunFile("config\\test.nut"); // test.nut is a custom foreign .nut file
    test.nut
    Code:
    // Scoopty woop
    //
    
    SqRunFile("config\\uiSkinFormImageSet.nut"); // This is actually found in ScriptInit.nut in the LPQ
    So if I successfully reverse out the LPQ ScriptInit.nut and populate a file on disk, I would be able to load the game as normal.
    However, by testing my files on disk as above, the game still runs, but when viewed in Process Explorer, a lot of files aren't loaded because
    our ScriptInit.nut doesn't have all the necessary information it needs. The LPQ ScriptInit.nut loads at least 20+ other .nut files, all of which don't have to have a constant name of uiSkinFormImageSet.nut, why, because we can specify a specific file in our script which could be FooBar.nut for all the engine cares and it'll load it, and FooBar could potentially do what it wants.

    It's looking better and better as time goes on.

  6. #4
    VXP's Avatar
    Join Date
    Apr 2012
    Gender
    male
    Posts
    17
    Reputation
    18
    Thanks
    4
    My Mood
    Drunk
    9/30/2018 - END OF THE MONTH PROGRESS UPDATE

    It's not because it's the end of the month, but I just so happened to make a breakthrough today.

    I can finally announce a tool that is well on its way to performing operations on LPQ files.
    However this time, I skipped the idea of DLL injection because that seemed like too much of a hassle, plus run-time and linking wise it was a nightmare.
    So what I've done now is created a tool that can actually call functions from LagerPacket.dll
    I've tested my tool with the Dragon God Resurrection and Divine Rebirth versions of the game. It seems like they didn't change LagerPacket.dll between versions so that's excellent for us.

    Ok now to the important part:

    lpq_tool.h
    Code:
    #ifndef LPQTOOL_H
    #define LPQTOOL_H 1
    #endif
    
    
    // Global Variables
    //
    struct LpqStruct
    {
        std::string header, buffer, data;
        const char* fname;
        char* cdata;
        bool validlpq;
        
        long read, fsize, offset;
        
        std::fstream* f_plpqfile;
    };
    
    
    // Function Prototypes
    //
    bool GetRightOf(std::string, std::string);
    
    
    // Function Definitions
    //
    bool GetRightOf(std::string o, std::string s)
    {
        int len = s.length();
        std::string a = o.substr(o.length() - len, len);
        
        if(a != ".lpq")
        {
            std::cout << " >>";
            return false;
        }
        
        std::cout << "\n";
        return true;
    }
    lpq_extractor.cpp
    Code:
    #include <iostream>
    #include <string>
    #include <fstream>
    #include <windows.h>
    
    #include "lpq_tool.h"
    
    // Prototype our class functions for later use...
    // -- NEEDS TO BE POPULATED MORE --
    class ILagerPacket
    {
        public:
            ILagerPacket(){};
            ~ILagerPacket(){};
    };
    class ILpqSystem
    {
        public:
            ILpqSystem();
            ~ILpqSystem();
    };
    class ILpqPatchBuilder
    {
        public:
            ILpqPatchBuilder();
            ~ILpqPatchBuilder();
    };
    class CLpqPatcher
    {
        public:
            CLpqPatcher(){};
            ~CLpqPatcher(){};
    };
    
    // Create function pointer references for later use...
    typedef unsigned __int32 (__cdecl *ICalcLpqCheckSum)(void*, unsigned __int32);
    typedef int (__thiscall *CCreatePatchFile)(void*);
    
    int main()
    {
        using namespace std;
        
        /*  ~~ For later use... ~~
        ILagerPacket LagerPacket;
        ILpqSystem LpqSystem;
        ILpqPatchBuilder LpqPatchBuilder;
        CLpqPatcher LpqPatcher;
        */
        
        // Load LagerPacket.dll
        HINSTANCE hLagerDLL = LoadLibrary("LagerPacket.dll");
        // If that failed..
        if(!hLagerDLL) {
            cout << "Could not find or load LagerPacket.dll\n"
                 << "Maybe LPQ_Extractor.exe isn't in your game's path?";
            system("PAUSE");
            return 0;
        }
        
        // Create CalcLpqCheckSum from DLL using our prototype function pointer from earlier...
        ICalcLpqCheckSum CalcLpqCheckSum = (ICalcLpqCheckSum)GetProcAddress(hLagerDLL, "?CalcLpqCheckSum@@YAKPAXK@Z");
        if (!CalcLpqCheckSum) {
            cout << "Could not locate ?CalcLpqCheckSum@@YAKPAXK@Z\n";
            system("PAUSE");
            return 0;
        }
        
        // Create CreatePatchFile from DLL using our prototype function pointer from earlier...
        CCreatePatchFile CreatePatchFile = (CCreatePatchFile)GetProcAddress(hLagerDLL, "?CreatePatchFile@CLpqPatcher@@UAEHXZ");
        if(!CreatePatchFile) {
            cout << "Could not locate ?CreatePatchFile@CLpqPatcher@@UAEHXZ\n";
            system("PAUSE");
            return 0;
        }
        
        // Pre-run testing to ensure that we can perform operations with LagerPacket.dll
        string buffer = "TEST", testFile = "test.lpq";
        int lpqcsy = 16660;
        void *pBuffer = &buffer, *p_testFile = &testFile;
        
        cout << "CalcLpqCheckSum(void *, unsigned long) returned " << CalcLpqCheckSum(pBuffer,lpqcsy) << " - (" << buffer << ", " << lpqcsy << ")\n"
             << "__TIME__: " << __TIME__ << endl;
        
        // Variables and initialize the LpqStruct structure
        string toolver = "v1.0.0.0", tab = "\x9", lpqname;
        LpqStruct LPQ;
        
        cout << "\n |*| LPQ Tool - LPQ Archive Zip/Unzip Utility - " << toolver << "\n"
             << " |*| Written by Karac Von Thweatt 2018\n\n"
             << " Enter the name of the LPQ file you wish to perform operations on.\n"
             << " NOTE: Must include \".lpq\" extension!\n"
             << " >>";
        
        // Get file name while extension is missing
        do
        {
            // Get LPQ file name in lpqname
            getline(cin, lpqname);
            // cin.clear() so automatic newline
            cin.clear();
        }
        while(!GetRightOf(lpqname, ".lpq"));
        
        // Swap char* to string then open file
        LPQ.fname = lpqname.c_str();
        ifstream LpqFile(LPQ.fname, ios::in | ios::binary);
        // If that failed..
        if(!LpqFile.is_open())
        {
            cout << " Couldn't open " << lpqname << " for input.\n"
                 << " Check to see if lpq_tool is in your game's directory.\n\n";
            system("PAUSE");
            return 0;
        }
        
        // Set up LPQ structure variable
        LPQ.offset = 0;
        
        // Read file into LPQ.data
        LpqFile.seekg(LPQ.offset, std::ios::end);   // Go to end of file
        LPQ.fsize = LpqFile.tellg();                // Report size in bytes
        LpqFile.seekg(LPQ.offset, std::ios::beg);   // Go to beginning
        LPQ.cdata = new char[LPQ.fsize];            // Set size of LPQ.cdata
        LpqFile.read(LPQ.cdata, LPQ.fsize);         // Read data to LPQ.cdata
        LPQ.data = LPQ.cdata;                       // Swap char to string
        delete[] LPQ.cdata;                         // Delete old char
        LpqFile.close();                            // Close file.
        
        // Report size of LPQ in bytes
        cout << " Size of " << lpqname << " :: " << LPQ.fsize << " bytes\n";
        
        // Set void pointer pBuffer to string address
        pBuffer = &LPQ.data;
        // Run CalcLpqCheckSum()
        cout << " CalcLpqCheckSum(); returned " << CalcLpqCheckSum(pBuffer,lpqcsy) << " - __TIME__: " << __TIME__ << "\n\n";
        
        // -- FOR LATER USE --
        // CreatePatchFile(p_testFile);
        
        system("PAUSE");    
        return 0;
    }
    So basically with CalcLpqCheckSum() we can perform a checksum on string data. Still have to delve further into LagerPacket.dll with IDA Pro to figure out just how the algorithm works, whether it uses __TIME__ or not, but I assumed and it doesn't really hurt anything anyway. Until I find out tho I'll leave the time stamp in the output.

    So here's the functions we're looking for:

    CreatePatchFile(void*)
    ExtendPatchFile(void*)
    ProceedPatch(char*)
    DeletePatchBlockFile(char*)
    SetLpqCallback(unsigned long, int (*) (LagerPacket *, int, int))
    // LagerPacket is a class if you didn't already know

    Variables:
    pBuffer, buffer, size - All unknown type; I think pBuffer is int* to &nBuffer
    int nBuffer
    ILpqSystem * g_piLpqSystem
    __security_cookie
    int g_nLpqErrorCode

    EDIT: -- ITS BEEN CONFIRMED THAT LPQ'S ARE COMPRESSED WITH 2008 LZO v0.22 --
    However, further details on compression are unknown.
    Last edited by VXP; 09-30-2018 at 11:08 PM. Reason: Forgot to throw in recently found property of LPQs

  7. #5
    Lionmirage's Avatar
    Join Date
    Dec 2018
    Gender
    male
    Posts
    1
    Reputation
    10
    Thanks
    0

    You are crazy!

    Ehi mate, you must be crazy! I always had your same idea but never really got to it! Totally with you on this project.
    At what point is it? like is it far from done? I am a PHP developer so is there any part i could help solving?
    Let me know

  8. #6
    Fasthackeromg's Avatar
    Join Date
    Jul 2013
    Gender
    male
    Location
    In your mind
    Posts
    14
    Reputation
    10
    Thanks
    0
    My Mood
    Inspired
    i love you if this happens

    edit: there is another guy working on this, you could make a team https://www.facebook.com/groups/1009866965705001/
    Last edited by Fasthackeromg; 01-05-2019 at 08:36 AM.

  9. #7
    VXP's Avatar
    Join Date
    Apr 2012
    Gender
    male
    Posts
    17
    Reputation
    18
    Thanks
    4
    My Mood
    Drunk
    That guy on Facebook is me lol, also will be updating with new progress report in the coming months. I'm still working on getting an apartment. I have the money but then after that I have to buy a new pc.
    Last edited by VXP; 03-16-2019 at 03:33 AM. Reason: Meant to reply more, am drunk atm (new pc not pic*** urgh)

  10. The Following User Says Thank You to VXP For This Useful Post:

    RobotGamingTV (04-28-2019)

  11. #8
    RobotGamingTV's Avatar
    Join Date
    Jul 2017
    Gender
    male
    Posts
    1
    Reputation
    10
    Thanks
    0
    My Mood
    Busy
    Quote Originally Posted by VXP View Post
    That guy on Facebook is me lol, also will be updating with new progress report in the coming months. I'm still working on getting an apartment. I have the money but then after that I have to buy a new pc.
    Ok so I managed to get a connection to my local server but the ports keep switching >_> Very annoying to have this.
    But atleast I got a connection.

  12. #9
    VXP's Avatar
    Join Date
    Apr 2012
    Gender
    male
    Posts
    17
    Reputation
    18
    Thanks
    4
    My Mood
    Drunk
    There's pretty much one solution to this. Listen to all ports, wait for a connection, then open the connection to the given client per requested port.

    Are you able to serve the Login.exe? If you're trying to write a server you'll need to serve all parts of the game including the update application, at least to serve a worldlist.xml. Beyond that, I still have to reverse the patchfile.lst and figure out exactly how that's working. I believe it to be a type of spreadsheet or CSV where it includes file names, dates, versions and other information relevant to patching out of date files.

    I'm in the process of writing up a full document for development purposes. Writing the server is cool and all, but reverse engineering the LPQ compression is crucial. Those LPQs contain critical client-server communication information which may make it a lot easier to reverse the server from scratch.

    There is also the Chinese server, however I can't access my Chinese KoK3 until I get a USB->SATA adapter to connect my old hard drives to my new pc.

  13. #10
    VXP's Avatar
    Join Date
    Apr 2012
    Gender
    male
    Posts
    17
    Reputation
    18
    Thanks
    4
    My Mood
    Drunk
    UPDATE TIME!

    Ok, I've made some progress with the server, however LPQs are still under scrutiny.

    I've figured out how the LPQ system works. The ILagerPacket int function creates a global int pointer called g_piLpqSystem.
    When it loads the files, it does the magic necessary to create legible data in memory, however the data is scrambled so to speak.

    DFM files in memory remain relatively intact after decompression, however Squirrel script .NUT files get wildly scrambled, and the game's internal Squirrel VM interpreter knows how to piece these together. There are 2 solutions:

    I can either brute force reverse out the Squirrel scripts by manually piecing them together from my string dump,
    or LoadLibrary the LagerPacket.dll in a custom program and try to get the game to load an LPQ file individually.

    The problem with solution #2 is while I easily avoid having thousands of excess strings, I'm still left with 2 issues:

    ILpqSystem loads LPQ files in a loop based on file paths provided. It knows to convert the directory paths into file names. WE.exe provides the paths. If I run the loading function from my custom application, I can provide a single file which can eliminate thousands more excess strings and binary data.
    The second issue is I'm still having trouble referencing the correct function from the DLL.

    I know for a fact that config.lpq and interface.lpq and their respecting *update.lpq files contain Squirrel scripts, and I recently identified a DFM form called NetPacket something or other, I forget at the moment and don't have anything open. Regardless, those are what I'm after.

    I also discovered the game uses multiple archiving APIs - namely ZLIB, LZMA, GZIP, and DEFLATE. It also uses Adler32 and CRC32.

    As for server side - I am able to receive communication from the client, but I'm still at step 1 after accepting the connection.
    Any time a recv() call is made on the socket, it always returns 3-4 bytes. I've tried sending back all sorts of data but obviously throwing garbage at it wont do any good.

    Anyone, if you feel like giving this project a crack, please, by all means. And please share any findings here.
    I'm still yet to obtain a different response

  14. #11
    VXP's Avatar
    Join Date
    Apr 2012
    Gender
    male
    Posts
    17
    Reputation
    18
    Thanks
    4
    My Mood
    Drunk

    King of Kings IV

    UPDATE TIME!

    And with a special announcement,
    King of Kings IV


    A lot has happened since April of 2019.
    Not just for me but all of us it seems.

    I have finally had a major breakthrough, with special thanks to "cxmu", a guy I just happened to find in a ******* server
    who took interest in kok3. I requested help and he obliged with using a config.lpq I sent him. Within minutes, he told me
    that he saw an LZO header, or what "looks like a header". I was amazed and confused. Totally stupefied.

    Turns out, LPQ is just a container. More research is being conducted on the LPQ data format but beyond it being a container,
    it is known for a fact that LPQ files contain a set of valid LZO streams. The thing is, we are still trying to
    configure LZO to handle GB2312 character encoding that would be originally plaintext.

    By simply trying to initialize LZO streams at different byte locations within an LPQ file, LZO will automatically try/catch
    in a loop and attempt the Decompress() method at the given location in the LPQ.


     

    Code:
    // ** lzo.net **
    // Copyright (c) 2017, Bianco Veigel
    //
    // Permission is hereby granted, free of charge, to any person obtaining a
    // copy of this software and associated documentation files (the "Software"),
    // to deal in the Software without restriction, including without limitation
    // the rights to use, copy, modify, merge, publish, distribute, sublicense,
    // and/or sell copies of the Software, and to permit persons to whom the
    // Software is furnished to do so, subject to the following conditions:
    //
    // The above copyright notice and this permission notice shall be included in
    // all copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    // DEALINGS IN THE SOFTWARE.
    //
    
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.IO.Compression;
    using System.Threading;
    using lzo.net;
    
    namespace lzo.net
    {
        /// <summary>
        /// Wrapper Stream for lzo compression
        /// </summary>
        public class LzoStream : Stream
        {
            protected readonly Stream Source;
            private long? _length;
            private readonly bool _leaveOpen;
            protected byte[] DecodedBuffer;
            protected const int MaxWindowSize = (1 << 14) + ((255 & 8) << 11) + (255 << 6) + (255 >> 2);
            protected RingBuffer RingBuffer = new RingBuffer(MaxWindowSize);
            protected long OutputPosition;
            protected int Instruction;
            protected LzoState State;
    
            protected enum LzoState
            {
                /// <summary>
                /// last instruction did not copy any literal 
                /// </summary>
                ZeroCopy = 0,
                /// <summary>
                /// last instruction used to copy between 1 literal 
                /// </summary>
                SmallCopy1 = 1,
                /// <summary>
                /// last instruction used to copy between 2 literals 
                /// </summary>
                SmallCopy2 = 2,
                /// <summary>
                /// last instruction used to copy between 3 literals 
                /// </summary>
                SmallCopy3 = 3,
                /// <summary>
                /// last instruction used to copy 4 or more literals 
                /// </summary>
                LargeCopy = 4
            }
    
            /// <summary>
            /// creates a new lzo stream for decompression
            /// </summary>
            /// <param name="stream">the compressed stream</param>
            /// <param name="mode">currently only decompression is supported</param>
            public LzoStream(Stream stream, CompressionMode mode)
                : this(stream, mode, false) { }
    
            /// <summary>
            /// creates a new lzo stream for decompression
            /// </summary>
            /// <param name="stream">the compressed stream</param>
            /// <param name="mode">currently only decompression is supported</param>
            /// <param name="leaveOpen">true to leave the stream open after disposing the LzoStream object; otherwise, false</param>
            public LzoStream(Stream stream, CompressionMode mode, bool leaveOpen)
            {
                if (mode != CompressionMode.Decompress)
                    throw new NotSupportedException("Compression is not supported");
                if (!stream.CanRead)
                    throw new ArgumentException("Write-only stream cannot be used for decompression");
                Source = stream;
                if (!(stream is BufferedStream))
                    Source = new BufferedStream(stream);
                _leaveOpen = leaveOpen;
                DecodeFirstByte();
            }
    
            private void DecodeFirstByte()
            {
                Instruction = Source.ReadByte();
                if (Instruction == -1)
                    throw new EndOfStreamException();
                if (Instruction > 15 && Instruction <= 17)
                {
                    throw new Exception();
                }
            }
    
            private void Copy(byte[] buffer, int offset, int count)
            {
                Debug.Assert(count > 0);
                do
                {
                    var read = Source.Read(buffer, offset, count);
                    if (read == 0)
                        throw new EndOfStreamException();
                    RingBuffer.Write(buffer, offset, read);
                    offset += read;
                    count -= read;
                } while (count > 0);
            }
    
            protected virtual int Decode(byte[] buffer, int offset, int count)
            {
                Debug.Assert(count > 0);
                Debug.Assert(DecodedBuffer == null);
                int read;
                var i = Instruction >> 4;
                switch (i)
                {
                    case 0://Instruction <= 15
                        {
                            /*
                             * Depends on the number of literals copied by the last instruction.                 
                             */
                            switch (State)
                            {
                                case LzoState.ZeroCopy:
                                    {
                                        /*
                                         * this encoding will be a copy of 4 or more literal, and must be interpreted
                                         * like this :                         * 
                                         * 0 0 0 0 L L L L  (0..15)  : copy long literal string
                                         * length = 3 + (L ?: 15 + (zero_bytes * 255) + non_zero_byte)
                                         * state = 4  (no extra literals are copied)
                                         */
                                        var length = 3;
                                        if (Instruction != 0)
                                        {
                                            length += Instruction;
                                        }
                                        else
                                        {
                                            length += 15 + ReadLength();
                                        }
                                        State = LzoState.LargeCopy;
                                        if (length <= count)
                                        {
                                            Copy(buffer, offset, length);
                                            read = length;
                                        }
                                        else
                                        {
                                            Copy(buffer, offset, count);
                                            DecodedBuffer = new byte[length - count];
                                            Copy(DecodedBuffer, 0, length - count);
                                            read = count;
                                        }
                                        break;
                                    }
                                case LzoState.SmallCopy1:
                                case LzoState.SmallCopy2:
                                case LzoState.SmallCopy3:
                                    read = SmallCopy(buffer, offset, count);
                                    break;
                                case LzoState.LargeCopy:
                                    read = LargeCopy(buffer, offset, count);
                                    break;
                                default:
                                    throw new ArgumentOutOfRangeException();
                            }
                            break;
                        }
                    case 1://Instruction < 32
                        {
                            /*
                             * 0 0 0 1 H L L L  (16..31)
                             * Copy of a block within 16..48kB distance (preferably less than 10B)
                             * length = 2 + (L ?: 7 + (zero_bytes * 255) + non_zero_byte)
                             * Always followed by exactly one LE16 :  D D D D D D D D : D D D D D D S S
                             * distance = 16384 + (H << 14) + D
                             * state = S (copy S literals after this block)
                             * End of stream is reached if distance == 16384
                             */
                            int length = (Instruction & 0x7) + 2;
                            if (length == 2)
                            {
                                length += 7 + ReadLength();
                            }
                            var s = Source.ReadByte();
                            var d = Source.ReadByte();
                            if (s != -1 && d != -1)
                            {
                                d = ((d << 8) | s) >> 2;
                                var distance = 16384 + ((Instruction & 0x8) << 11) | d;
                                if (distance == 16384)
                                    return -1;
    
                                read = CopyFromRingBuffer(buffer, offset, count, distance, length, s & 0x3);
                                break;
                            }
                            throw new EndOfStreamException();
                        }
                    case 2://Instruction < 48
                    case 3://Instruction < 64
                        {
                            /*
                             * 0 0 1 L L L L L  (32..63)
                             * Copy of small block within 16kB distance (preferably less than 34B)
                             * length = 2 + (L ?: 31 + (zero_bytes * 255) + non_zero_byte)
                             * Always followed by exactly one LE16 :  D D D D D D D D : D D D D D D S S
                             * distance = D + 1
                             * state = S (copy S literals after this block)
                             */
                            int length = (Instruction & 0x1f) + 2;
                            if (length == 2)
                            {
                                length += 31 + ReadLength();
                            }
                            var s = Source.ReadByte();
                            var d = Source.ReadByte();
                            if (s != -1 && d != -1)
                            {
                                d = ((d << 8) | s) >> 2;
                                var distance = d + 1;
    
                                read = CopyFromRingBuffer(buffer, offset, count, distance, length, s & 0x3);
                                break;
                            }
                            throw new EndOfStreamException();
                        }
                    case 4://Instruction < 80
                    case 5://Instruction < 96
                    case 6://Instruction < 112
                    case 7://Instruction < 128
                        {
                            /*
                             * 0 1 L D D D S S  (64..127)
                             * Copy 3-4 bytes from block within 2kB distance
                             * state = S (copy S literals after this block)
                             * length = 3 + L
                             * Always followed by exactly one byte : H H H H H H H H
                             * distance = (H << 3) + D + 1
                             */
                            var length = 3 + ((Instruction >> 5) & 0x1);
                            var result = Source.ReadByte();
                            if (result != -1)
                            {
                                var distance = (result << 3) + ((Instruction >> 2) & 0x7) + 1;
    
                                read = CopyFromRingBuffer(buffer, offset, count, distance, length, Instruction & 0x3);
                                break;
                            }
                            throw new EndOfStreamException();
                        }
                    default:
                        {
                            /*
                             * 1 L L D D D S S  (128..255)
                             * Copy 5-8 bytes from block within 2kB distance
                             * state = S (copy S literals after this block)
                             * length = 5 + L
                             * Always followed by exactly one byte : H H H H H H H H
                             * distance = (H << 3) + D + 1
                             */
                            var length = 5 + ((Instruction >> 5) & 0x3);
                            var result = Source.ReadByte();
                            if (result != -1)
                            {
                                var distance = (result << 3) + ((Instruction & 0x1c) >> 2) + 2;
    
                                read = CopyFromRingBuffer(buffer, offset, count, distance, length, Instruction & 0x3);
                                break;
                            }
                            throw new EndOfStreamException();
                        }
                }
                Instruction = Source.ReadByte();
                if (Instruction != -1)
                {
                    OutputPosition += read;
                    return read;
                }
                throw new EndOfStreamException();
            }
    
            private int LargeCopy(byte[] buffer, int offset, int count)
            {
                /*
                 *the instruction becomes a copy of a 3-byte block from the
                 * dictionary from a 2..3kB distance, and must be interpreted like this :
                 * 0 0 0 0 D D S S  (0..15)  : copy 3 bytes from 2..3 kB distance
                 * length = 3
                 * state = S (copy S literals after this block)
                 * Always followed by exactly one byte : H H H H H H H H
                 * distance = (H << 2) + D + 2049
                 */
                var result = Source.ReadByte();
                if (result != -1)
                {
                    var distance = (result << 2) + ((Instruction & 0xc) >> 2) + 2049;
    
                    return CopyFromRingBuffer(buffer, offset, count, distance, 3, Instruction & 0x3);
                }
                throw new EndOfStreamException();
            }
    
            private int SmallCopy(byte[] buffer, int offset, int count)
            {
                /* 
                 * the instruction is a copy of a
                 * 2-byte block from the dictionary within a 1kB distance. It is worth
                 * noting that this instruction provides little savings since it uses 2
                 * bytes to encode a copy of 2 other bytes but it encodes the number of
                 * following literals for free. It must be interpreted like this :
                 * 
                 * 0 0 0 0 D D S S  (0..15)  : copy 2 bytes from <= 1kB distance
                 * length = 2
                 * state = S (copy S literals after this block)
                 * Always followed by exactly one byte : H H H H H H H H
                 * distance = (H << 2) + D + 1
                 */
                var h = Source.ReadByte();
                if (h != -1)
                {
                    var distance = (h << 2) + ((Instruction & 0xc) >> 2) + 1;
    
                    return CopyFromRingBuffer(buffer, offset, count, distance, 2, Instruction & 0x3);
                }
    
                throw new EndOfStreamException();
            }
    
            private int ReadLength()
            {
                int b;
                int length = 0;
                while ((b = Source.ReadByte()) == 0)
                {
                    if (length >= Int32.MaxValue - 1000)
                    {
                        throw new Exception();
                    }
                    length += 255;
                }
                if (b != -1) return length + b;
                throw new EndOfStreamException();
            }
    
            private int CopyFromRingBuffer(byte[] buffer, int offset, int count, int distance, int copy, int state)
            {
                Debug.Assert(copy >= 0);
                var result = copy + state;
                State = (LzoState)state;
                if (count >= result)
                {
                    var size = copy;
                    if (copy > distance)
                    {
                        size = distance;
                        RingBuffer.Copy(buffer, offset, distance, size);
                        copy -= size;
                        var copies = copy / distance;
                        for (int i = 0; i < copies; i++)
                        {
                            Buffer.BlockCopy(buffer, offset, buffer, offset + size, size);
                            offset += size;
                            copy -= size;
                        }
                        if (copies > 0)
                        {
                            var length = size * copies;
                            RingBuffer.Write(buffer, offset - length, length);
                        }
                        offset += size;
                    }
                    if (copy > 0)
                    {
                        if (copy < size)
                            size = copy;
                        RingBuffer.Copy(buffer, offset, distance, size);
                        offset += size;
                    }
                    if (state > 0)
                    {
                        Copy(buffer, offset, state);
                    }
                    return result;
                }
    
                if (count <= copy)
                {
                    CopyFromRingBuffer(buffer, offset, count, distance, count, 0);
                    DecodedBuffer = new byte[result - count];
                    CopyFromRingBuffer(DecodedBuffer, 0, DecodedBuffer.Length, distance, copy - count, state);
                    return count;
                }
                CopyFromRingBuffer(buffer, offset, count, distance, copy, 0);
                var remaining = count - copy;
                DecodedBuffer = new byte[state - remaining];
                Copy(buffer, offset + copy, remaining);
                Copy(DecodedBuffer, 0, state - remaining);
                return count;
            }
    
            private int ReadInternal(byte[] buffer, int offset, int count)
            {
                Debug.Assert(count > 0);
                if (_length.HasValue && OutputPosition >= _length)
                    return -1;
                int read;
                if (DecodedBuffer == null)
                {
                    if ((read = Decode(buffer, offset, count)) >= 0) return read;
                    _length = OutputPosition;
                    return -1;
                }
                var decodedLength = DecodedBuffer.Length;
                if (count > decodedLength)
                {
                    Buffer.BlockCopy(DecodedBuffer, 0, buffer, offset, decodedLength);
                    DecodedBuffer = null;
                    OutputPosition += decodedLength;
                    return decodedLength;
                }
                Buffer.BlockCopy(DecodedBuffer, 0, buffer, offset, count);
                if (decodedLength > count)
                {
                    var remaining = new byte[decodedLength - count];
                    Buffer.BlockCopy(DecodedBuffer, count, remaining, 0, remaining.Length);
                    DecodedBuffer = remaining;
                }
                else
                {
                    DecodedBuffer = null;
                }
                OutputPosition += count;
                return count;
            }
    
            #region wrapped stream methods
    
            public override bool CanRead
            {
                get { return true; }
            }
    
            public override bool CanSeek { get { return false; } }
    
            public override bool CanWrite { get { return false; } }
    
            public override long Length
            {
                get
                {
                    if (_length.HasValue)
                        return _length.Value;
                    throw new NotSupportedException();
                }
            }
    
            public override long Position
            {
                get { return OutputPosition; }
                set
                {
                    if (OutputPosition == value) return;
                    Seek(value, SeekOrigin.Begin);
                }
            }
    
            public override void Flush()
            {
            }
    
            public override int Read(byte[] buffer, int offset, int count)
            {
                if (_length.HasValue && OutputPosition >= _length)
                    return 0;
                var result = 0;
                while (count > 0)
                {
                    var read = ReadInternal(buffer, offset, count);
                    if (read == -1)
                        return result;
                    result += read;
                    offset += read;
                    count -= read;
                }
                return result;
            }
    
            public override long Seek(long offset, SeekOrigin origin)
            {
                throw new NotImplementedException();
            }
    
            public override void SetLength(long value)
            {
                _length = value;
            }
    
            public override void Write(byte[] buffer, int offset, int count)
            {
                throw new InvalidOperationException("cannot write to readonly stream");
            }
    
            protected override void Dispose(bool disposing)
            {
                if (!_leaveOpen)
                    Source.Dispose();
                base.Dispose(disposing);
            }
    
            #endregion
        }
    }
    
    //
    // Copyright (c) 2017, Bianco Veigel
    //
    // Permission is hereby granted, free of charge, to any person obtaining a
    // copy of this software and associated documentation files (the "Software"),
    // to deal in the Software without restriction, including without limitation
    // the rights to use, copy, modify, merge, publish, distribute, sublicense,
    // and/or sell copies of the Software, and to permit persons to whom the
    // Software is furnished to do so, subject to the following conditions:
    //
    // The above copyright notice and this permission notice shall be included in
    // all copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    // DEALINGS IN THE SOFTWARE.
    //
    
    namespace lzo.net
    {
        /// <summary>
        /// fixed sized ring buffer
        /// </summary>
        public class RingBuffer
        {
            private readonly byte[] _buffer;
            private int _position;
            private readonly int _size;
    
            /// <summary>
            /// create a new RingBuffer with the specified size
            /// </summary>
            /// <param name="size">the size of the buffer</param>
            public RingBuffer(int size)
            {
                _buffer = new byte[size];
                _size = size;
            }
    
            /// <summary>
            /// set the position relative to the current position
            /// </summary>
            /// <remarks>wraps the position of the end is reached</remarks>
            /// <param name="offset">relative offset</param>
            public void Seek(int offset)
            {
                _position += offset;
                if (_position > _size)
                {
                    do
                    {
                        _position -= _size;
                    } while (_position > _size);
                    return;
                }
                while (_position < 0)
                {
                    _position += _size;
                }
            }
    
            /// <summary>
            /// copies as sequence of bytes from the Ringbuffer at the specified distance into the buffer and also the RingBuffer itself
            /// </summary>
            /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the RingBuffer</param>
            /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the RingBuffer</param>
            /// <param name="distance">The distance to seek backwards before starting to copy</param>
            /// <param name="count">The maximum number of bytes to be read from the RingBuffer</param>
            public void Copy(byte[] buffer, int offset, int distance, int count)
            {
                if (_position - distance > 0 && _position + count < _size)
                {
                    if (count < 10)
                    {
                        do
                        {
                            var value = _buffer[_position - distance];
                            _buffer[_position++] = value;
                            buffer[offset++] = value;
                        } while (--count > 0);
                    }
                    else
                    {
                        Buffer.BlockCopy(_buffer, _position - distance, buffer, offset, count);
                        Buffer.BlockCopy(buffer, offset, _buffer, _position, count);
                        _position += count;
                    }
                }
                else
                {
                    Seek(-distance);
                    Read(buffer, offset, count);
                    Seek(distance - count);
                    Write(buffer, offset, count);
                }
            }
    
            /// <summary>
            /// reads a sequence of bytes from the RingBuffer and advances the position within the RingBuffer by the number of bytes read
            /// </summary>
            /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the RingBuffer</param>
            /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the RingBuffer</param>
            /// <param name="count">The maximum number of bytes to be read from the RingBuffer</param>
            public void Read(byte[] buffer, int offset, int count)
            {
                if (count < 10 && (_position + count) < _size)
                {
                    do
                    {
                        buffer[offset++] = _buffer[_position++];
                    } while (--count > 0);
                }
                else
                {
                    while (count > 0)
                    {
                        var copy = _size - _position;
                        if (copy > count)
                        {
                            Buffer.BlockCopy(_buffer, _position, buffer, offset, count);
                            _position += count;
                            break;
                        }
                        Buffer.BlockCopy(_buffer, _position, buffer, offset, copy);
                        _position = 0;
                        count -= copy;
                        offset += copy;
                    }
                }
            }
    
            /// <summary>
            /// writes a sequence of bytes to the RingBuffer and advances the current position within this RingBuffer by the number of bytes written
            /// </summary>
            /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the RingBuffer.</param>
            /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the RingBuffer.</param>
            /// <param name="count">The number of bytes to be written to the RingBuffer.</param>
            public void Write(byte[] buffer, int offset, int count)
            {
                if (count < 10 && (_position + count) < _size)
                {
                    do
                    {
                        _buffer[_position++] = buffer[offset++];
                    } while (--count > 0);
                }
                else
                {
                    while (count > 0)
                    {
                        var cnt = _size - _position;
                        if (cnt > count)
                        {
                            Buffer.BlockCopy(buffer, offset, _buffer, _position, count);
                            _position += count;
                            return;
                        }
                        Buffer.BlockCopy(buffer, offset, _buffer, _position, cnt);
                        _position = 0;
                        offset += cnt;
                        count -= cnt;
                    }
                }
            }
    
            /// <summary>
            /// creates a deep clone
            /// </summary>
            /// <returns></returns>
            public RingBuffer Clone()
            {
                var result = new RingBuffer(_size) { _position = _position };
                Buffer.BlockCopy(_buffer, 0, result._buffer, 0, _size);
                return result;
            }
        }
    }
    
    namespace LPQ_Extractor
    {
        class Program
        {
            public static int Main()
            {
                long b;
                var to = new MemoryStream();
                var from = File.Open("config.lpq", FileMode.Open);
                var decompress = new LzoStream(from, CompressionMode.Decompress);
    
                // -- TODO --
                // Modify lzo.net in ways that achieve more intelligeble or accurate target data.
    
                Console.WriteLine(" -- King of Kings III - LPQ Extract Tool - v0.1.2.9");
                Console.WriteLine(" -- Written in part by \"cxmu\" and Karac AKA \"Gorensuav\" Thweatt");
                Console.WriteLine(" --\n\n");
    
                Console.WriteLine(" [x] Enter the name of the LPQ file you would like to extract:\n [!] Example: config.lpq\n");
                string lpqstr = Console.ReadLine();
    
                Console.WriteLine("\n\n [i] Activity: Checking if " + lpqstr + " exists...");
                
                // Thread.Sleep(1);
    
                string sPath = Directory.GetCurrentDirectory(), oPath, nPath;
    
                // check if sPath exists
    
                Console.WriteLine($" [i] Information: Current active directory for search: {sPath}");
    
                oPath = sPath;
                nPath = oPath + "\\" + lpqstr.Substring(0, lpqstr.Length - 4);
                sPath += "\\" + lpqstr;
                bool lpqexist = File.Exists(sPath);
    
                // still a few missing things (create directory if does not exist, etc)
    
                if(!lpqexist)
                {
                    Console.WriteLine($"\n [E] Fatal Error: Cannot locate \"{lpqstr}\" does not exist in {oPath}. Terminating...\n");
                }
                else
                {
                    Console.WriteLine($"\n [i] Information: Located \"{lpqstr}\"");
                    Console.WriteLine($" [i] Activity: Opening \"{lpqstr}\" for extraction...");
                }
    
                Thread.Sleep(1000);
    
                for (b = 0; b < from.Length; b += 2)
                {
                    from.Seek(b, SeekOrigin.Begin);
                    try
                    {
                        to = new MemoryStream();
                        Console.WriteLine($" [$] Currently at byte: {b}");
    
                        decompress.CopyTo(to);
                        File.WriteAllBytes(lpqstr + "\\" + lpqstr + $"__{b+1}.txt", to.ToArray());
                    }
                    catch
                    {
                        Console.WriteLine($" [!] Warning: Invalid stream data at byte: {b}");
                        b++;
                    }
                }
                from.Close();
    
                return 0;
            }
        }
    }


    You can find out more about King of Kings IV by visiting the official website here:
    https://www.kingofkings4.com/

    I'm the lead developer and I regularly provide updates that improve or expand the game towards becoming a spiritual
    successor to kok3. I've been working on it since around the beginning of this year and recently just upgraded my PC.
    Now progress is really flying. I most recently released v0.0.2.8 of the test client series which allow you to check in on
    the progress being made. Version 0.0.3.1 is approaching fast and I'm trying to get multiplayer set up with a server in
    time for July 1st which will be the official closed alpha, only open to 100 people plus myself. There are about 60 spots
    left. You can find out how to confirm a spot on the website.

    https://www.kingofkings4.com/img/sewer1.png
    Check out my official King of Kings III reverse engineering project thread!

    https://www.mpgh.net/forum/showthread.php?t=1367141

  15. #12
    VXP's Avatar
    Join Date
    Apr 2012
    Gender
    male
    Posts
    17
    Reputation
    18
    Thanks
    4
    My Mood
    Drunk

    Cool

    Okay, a lot of people have been waiting for an update.

    Here we are.

    Here's what I know so far and where we all stand on bringing King of Kings 3 back to life.



    My current and most credible theory is that we'll have to totally unpack King of Kings 3 to get an original source exe.

    Currently KoK3 is too obfuscated and broken into too many chunks to follow it properly through IDA, also, a lot of functions are created with VMs inside, the core VM which we need to access is SquirrelVM, which is the game's core scripting engine (cannot be unpacked as it is part of the game's engine)

    I will be taking the time now to find all of the utilities needed and one by one crack kok3.

    Once significant enough progress has been made, I will post another update.

    Lend me your strength and determination, everyone. This is gonna be some heavy duty work.

    ** EDIT **
    Every time I look up one of these unpackers, there's a result and tons of resources available to cross check work, so currently, although daunting, the list just may be able to be crunched significantly.

    We will be seeing the first results within the next 2 weeks.
    Last edited by VXP; 3 Weeks Ago at 12:35 AM.
    Check out my official King of Kings III reverse engineering project thread!

    https://www.mpgh.net/forum/showthread.php?t=1367141

Similar Threads

  1. [Assembly Tutorial] 40 Reverse Engineering
    By radnomguywfq3 in forum Programming Tutorials
    Replies: 29
    Last Post: 04-02-2020, 01:52 PM
  2. [Release] Crossfire > King < Have a look on this thread please :)
    By speedo_edoo in forum CrossFire Europe Discussions
    Replies: 3
    Last Post: 03-25-2016, 10:44 AM
  3. Warface Reverse Engineering, Addresses and Resources Thread
    By _PuRe.LucK* in forum Warface Coding & Source Code
    Replies: 2
    Last Post: 01-30-2015, 05:24 AM
  4. Replies: 0
    Last Post: 10-18-2008, 06:06 PM
  5. Reverse Engineering!
    By Jeckels in forum WarRock - International Hacks
    Replies: 13
    Last Post: 11-06-2007, 09:45 PM

Tags for this Thread