#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "fs_private.h"

/* NOT_FROM_ORIGINAL_SOURCE: local factoring of repeated pack-reference flags. */
static void coduo_fs_maybe_mark_pack_references(pack_t *pack,
                                                char *qpath)
{
    size_t length;

    length = strlen(qpath);
    /* ORIGINAL_BINARY_BUG: stock subtracts these suffix widths without first
     * proving qpath is long enough; see
     * original-bugs/coduomp-pack-reference-short-name-underread.md. */
    if (pack->generalReference == 0 &&
        Q_stricmp(qpath + length - 7, ".shader") != 0 &&
        Q_stricmp(qpath + length - 4, ".txt") != 0 &&
        Q_stricmp(qpath + length - 4, ".cfg") != 0 &&
        Q_stricmp(qpath + length - 7, ".config") != 0 &&
        strstr(qpath, "levelshots") == NULL &&
        Q_stricmp(qpath + length - 4, ".bot") != 0 &&
        Q_stricmp(qpath + length - 6, ".arena") != 0 &&
        Q_stricmp(qpath + length - 5, ".menu") != 0 &&
        strstr(qpath, "soundaliases") == NULL) {
        pack->generalReference = 1;
    }

    if (pack->gameModuleReference == 0 &&
        FS_ShiftedStrStr(qpath, "{usmgskesve~><4jrr", (char)-6) != NULL) {
        pack->gameModuleReference = 1;
    }
    if (pack->cgameModuleReference == 0 &&
        FS_ShiftedStrStr(qpath, "wqaeicogaoraz:80fnn", (char)-2) != NULL) {
        pack->cgameModuleReference = 1;
    }
    if (pack->uiModuleReference == 0 &&
        FS_ShiftedStrStr(qpath, "ztdzndrud}=;3iqq", (char)-5) != NULL) {
        pack->uiModuleReference = 1;
    }
}

int32_t FS_FOpenFileRead_Internal(char *qpath, int32_t *handle,
                                  int32_t uniqueFile, int32_t quiet)
{
    char copyPath[FS_OSPATH_SIZE];
    char osPath[FS_OSPATH_SIZE];
    FILE *file;
    char *extension;
    searchpath_t *search;
    pack_t *pack;
    directory_t *dir;
    fileInPack_t *pakFile;
    pack_t *impurePack;
    coduo_fs_zip_io_file_t *zipStream;
    FILE *zipFile;
    int32_t hash;

    hash = 0;
    FS_CheckFileSystemStarted();

    if (handle == NULL) {
        for (search = FS_SearchpathHead(); search != NULL;
             search = FS_SearchpathNext(search)) {
            if (FS_UseSearchPath(search) == 0) {
                continue;
            }

            pack = search->pack;
            if (pack != NULL) {
                hash = (int32_t)FS_HashFileName(qpath, pack->hashSize);
            }

            pakFile = (pack != NULL) ? pack->hashTable[hash] : NULL;
            if (pack == NULL || pakFile == NULL) {
                dir = search->dir;
                if (dir != NULL) {
                    FS_BuildOSPath_Internal(dir->path, dir->gamedir, qpath, osPath,
                                  quiet);
                    file = fopen(osPath, "rb");
                    if (file != NULL) {
                        fclose(file);
                        return 1;
                    }
                }
            } else {
                do {
                    if (FS_FilenameCompare(pakFile->name, qpath) == 0) {
                        return 1;
                    }
                    pakFile = pakFile->next;
                } while (pakFile != NULL);
            }
        }

        return 0;
    }

    while (*qpath == '/' || *qpath == '\\') {
        qpath++;
    }

    if (strstr(qpath, "..") != NULL || strstr(qpath, "::") != NULL) {
        *handle = 0;
        return -1;
    }

    *handle = FS_HandleForFile(quiet);
    fs_handleFiles[*handle].uniqueObject = uniqueFile;

    impurePack = NULL;
    for (search = FS_SearchpathHead(); search != NULL;
         search = FS_SearchpathNext(search)) {
        if (FS_UseSearchPath(search) == 0) {
            continue;
        }

        pack = search->pack;
        if (pack != NULL) {
            hash = (int32_t)FS_HashFileName(qpath, pack->hashSize);
        }

        if (strstr(qpath, ".bsp") != NULL) {
            pakFile = (pack != NULL) ? pack->hashTable[hash] : NULL;
            if (pack != NULL && pakFile != NULL) {
                Q_strncpyz(fs_gameDirVar, pack->pakGamename,
                           CODUO_FS_PACK_NAME_SIZE);
            } else {
                dir = search->dir;
                if (dir != NULL) {
                    Q_strncpyz(fs_gameDirVar, dir->gamedir,
                               CODUO_FS_PACK_NAME_SIZE);
                }
            }
        }

        pakFile = (pack != NULL) ? pack->hashTable[hash] : NULL;
        if (pack != NULL && pakFile != NULL) {
            do {
                if (FS_FilenameCompare(pakFile->name, qpath) == 0) {
                    if (search->localized != 0 ||
                        FS_PakIsPure(pack) != 0) {
                        coduo_fs_maybe_mark_pack_references(pack, qpath);

                        if (uniqueFile == 0) {
                            fs_handleFiles[*handle].ioObject = pack->zipFile;
                        } else {
                            fs_handleFiles[*handle].ioObject =
                                Unzip_CloneArchiveForPack(pack, pack->zipFile);
                            if (fs_handleFiles[*handle].ioObject == NULL) {
                                if (quiet != 0) {
                                    FS_FCloseFile(*handle);
                                    *handle = 0;
                                    return -1;
                                }
                                Com_Error(0, "\x15" "Couldn't reopen %s",
                                          pack);
                            }
                        }

                        Q_strncpyz(fs_handleFiles[*handle].name, qpath,
                                   CODUO_FS_HANDLE_NAME_SIZE);
                        fs_handleFiles[*handle].zipArchive = pack;
                        zipStream =
                            FS_LoadZipIOFile(fs_handleFiles[*handle].ioObject);
                        zipFile = zipStream->file;
                        Unzip_GoToFilePosition(pack->zipFile,
                                               pakFile->zipPosition);
                        if (zipStream != (coduo_fs_zip_io_file_t *)pack->zipFile) {
                            /* ORIGINAL_BINARY_BUG: this full record copy also
                             * copies a live shared pfile_in_zip_read pointer.
                             * Unzip_OpenCurrentFile below then closes it
                             * through the clone, leaving the shared archive
                             * dangling; see original-bugs/
                             * engine-unzip-unique-clone-shared-reader.md. */
                            /* The retail i386 copy is 0x80, exactly its whole
                             * coduo_unz_t. Native FILE/read-state pointers
                             * widen, so copy the complete host record before
                             * restoring the clone's distinct FILE below. */
                            Com_Memcpy(zipStream, pack->zipFile,
                                       sizeof(*zipStream));
                        }
                        zipStream->file = zipFile;
                        Unzip_OpenCurrentFile(FS_LoadZipIOFile(
                            fs_handleFiles[*handle].ioObject));
                        fs_handleFiles[*handle].zipRewindOffset =
                            pakFile->zipPosition;

                        if (fs_debug->integer != 0 && quiet == 0) {
                            Com_Printf("FS_FOpenFileRead: %s (found in '%s')\n",
                                       qpath, pack);
                        }
                        return (int32_t)
                            zipStream->cur_file_info.uncompressed_size;
                    }

                    impurePack = pack;
                    break;
                }

                pakFile = pakFile->next;
            } while (pakFile != NULL);
        } else if (search->dir != NULL) {
            extension = FS_GetExtension(qpath);
            if ((fs_restrict->integer == 0 && fs_numServerPaks == 0) ||
                search->localized != 0 || FS_IsAllowedExtension(extension) != 0) {
                dir = search->dir;
                FS_BuildOSPath_Internal(dir->path, dir->gamedir, qpath, osPath, quiet);

                fs_handleFiles[*handle].ioObject = fopen(osPath, "rb");
                if (fs_handleFiles[*handle].ioObject != NULL) {
                    if (search->localized == 0 &&
                        FS_IsAllowedExtension(extension) == 0) {
                        fs_fakeChkSum = rand() + 1;
                    }

                    Q_strncpyz(fs_handleFiles[*handle].name, qpath,
                               CODUO_FS_HANDLE_NAME_SIZE);
                    fs_handleFiles[*handle].zipArchive = NULL;

                    if (fs_debug->integer != 0 && quiet == 0) {
                        Com_Printf(
                            "FS_FOpenFileRead: %s (found in '%s/%s')\n",
                            qpath, dir->path, dir->gamedir);
                    }

                    if (fs_copyfiles->integer != 0 &&
                        Q_stricmp(dir->path,
                                     fs_cdpath->string) ==
                            0) {
                        FS_BuildOSPath_Internal(fs_basepath->string,
                                     dir->gamedir, qpath, copyPath, quiet);
                        FS_Copyfiles(osPath, copyPath);
                    }

                    return FS_filelength(*handle);
                }
            }
        }
    }

    if (fs_debug->integer != 0 && quiet == 0) {
        Com_Printf("Can't find %s\n", qpath);
    }

    *handle = 0;
    if (impurePack != NULL) {
        Com_Error(1, va("EXE_UNPURECLIENTDETECTED\x15\n%s", impurePack));
    }

    return -1;
}
