Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions MGSM2Fix.ini
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ RestoreGhosts = true
RestoreMedicine = true


; MGS1 only: The collection replaces the game's Option -> SCREEN help texture
; with a four-line version, dropping the line that names the button used to go
; back - fair enough, since that button's name is not the same on every
; platform. What it gets wrong is re-centring the four lines it keeps in the
; same canvas: that draws them 12 pixels below where the game draws them, and
; leaves a flat black band over the grey ramp above them.
; fixed - the collection's four lines, put back where the game draws
; them, and no band
; original - the game's own six lines, in that same place
; collection - leave the collection's version exactly as it is
; Only MGS1 (USA) is covered so far; other versions need their own offsets.
BrightnessText = fixed

; MGS1 only: The collection rewrites the game's whole 16-bit configuration word
; every frame to impose its button-type setting, from a copy it read a moment
Expand Down
1 change: 1 addition & 0 deletions MGSM2Fix.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
<ClInclude Include="src\extern\json\include\nlohmann\json_fwd.hpp" />
<ClInclude Include="src\m2fix\ketchup.h" />
<ClInclude Include="src\m2fix\ram_patch.h" />
<ClInclude Include="src\m2fix\patch_range.h" />
<ClInclude Include="src\m2\epi.h" />
<ClInclude Include="src\m2fix\m2fixbase.h" />
<ClInclude Include="src\m2fix\m2game.h" />
Expand Down
3 changes: 3 additions & 0 deletions MGSM2Fix.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,9 @@
<ClInclude Include="src\m2fix\ram_patch.h">
<Filter>M2Fix</Filter>
</ClInclude>
<ClInclude Include="src\m2fix\patch_range.h">
<Filter>M2Fix</Filter>
</ClInclude>
<ClInclude Include="src\m2fix\m2fix.h">
<Filter>M2Fix</Filter>
</ClInclude>
Expand Down
122 changes: 122 additions & 0 deletions src/games/mgs1.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ class MGS1 : public M2Game
SQHook<Squirk::Standard>::SetPatchDataBlacklist(MGS1_DataBlacklist_Medicine);
}

if (M2Config::eBrightnessText != M2BrightnessText::Collection) {
// Scope the collection replacement to USA and the selected disc.
for (unsigned disk = 0; disk < MGS1_RangeBlacklist_BrightnessText.size(); ++disk) {
const auto &range = MGS1_RangeBlacklist_BrightnessText[disk];
SQHook<Squirk::Standard>::SetPatchRangeBlacklist(981, disk, range.first, range.second);
}
}

// `fixed` also writes back the two lines the collection was right to
// drop, blanked - see MGS1_BrightnessTextData.
if (M2Config::eBrightnessText == M2BrightnessText::Fixed) {
MGS1_KetchupPatches = MGS1_BrightnessTextPatches();
}

if (M2Config::bPatchesDisableFont) {
for (auto & MGS1_TextureWhitelist_Font : MGS1_TextureWhitelist_Fonts) {
SQHook<Squirk::Standard>::SetTextureWhitelist(MGS1_TextureWhitelist_Font);
Expand Down Expand Up @@ -95,6 +109,10 @@ class MGS1 : public M2Game
return &MGS1_Ketchup;
}

virtual std::vector<Ketchup_DiskPatch> *SQKetchupPatches() override
{
return &MGS1_KetchupPatches;
}

#ifndef _WIN64
virtual void GWRenderGeometry(int & gw_width, int & gw_height, int & fb_width, int & fb_height, int & img_width, int & img_height) override
Expand Down Expand Up @@ -200,6 +218,110 @@ class MGS1 : public M2Game

const std::string MGS1_FileBlacklist_Ghosts = "shinrei";

// The collection replaces the Option -> SCREEN help texture (`sc_text` in
// the `option` stage's archive) with a four-line version on the same 232x70
// canvas. Dropping "Press the O button to return to the option screen." is
// fair - that button's name is not the same on every platform - but it also
// re-centres the four lines it keeps, and that is a bug: the whole canvas is
// opaque (its quad is drawn with abe = 0), and the game's art paints canvas
// rows 0..3 one step off black, at (8,8,8), so they vanish into the grey
// ramp's own 8 band and hide the backdrop's top edge. Re-centring carries
// those four rows down with the text, leaving flat black over the 8 band.
// Measured on the collection's USA: 0.00 luminance inside the canvas
// against 8.89 beside it, four rows, with the text one full line pitch
// (12 game rows) below where the game draws it.
//
// These are the four pieces of that one archive entry on MGS1 (USA) disc 1,
// addressed by image offset, which is how the collection names them.
// Integral needs none: it has no `sc_text` of its own for them to replace.
const std::vector<std::string> MGS1_FileBlacklist_BrightnessText = {
"disc1_165A34CC", "disc1_165A3BD8", "disc1_165A4508", "disc1_165A4E38",
};

// The same entry as a disc-image range, per disk: `option` is STAGE.DIR
// sector 27023 on both of MGS1 (USA)'s disks, and the entry's payload is
// 5852 bytes at file offset 55499300, so the span follows from each disk's
// STAGE.DIR LBA (132344 and 100801). Tight enough to be safe, checked two
// ways against every patch candidate in the log archive: of the 107 the
// collection offers this title, exactly four fall anywhere inside the
// 81-sector option stage and they are the four above; and across titles
// 99 (Integral) and 980 (the Japanese MGS1), whose disc images do span
// these offsets, nothing falls inside either window. Registered with USA title 981 and a disc index; checked against the
// active title/disc when each collection patch is submitted.
const std::vector<std::pair<uint64_t, uint64_t>> MGS1_RangeBlacklist_BrightnessText = {
{ 0x165A34CCull, 0x165A4F38ull }, // disk 0
{ 0x11EE2B7Cull, 0x11EE45E8ull }, // disk 1
};

// Rows 46..69 of that texture, re-encoded blank. Splicing these over the
// game's own payload leaves rows 0..45 - the four lines the collection also
// keeps, and the (8,8,8) filler - byte for byte as the game has them, and
// drops lines 5 and 6. The payload's declared size does not change: PCX
// here is four bit planes run-length encoded one row at a time, so rows are
// independent in the byte stream, this encoding is shorter than the one it
// replaces, and the decoder stops after 70 rows without reading the rest.
//
// Why not move the quad instead: the texture's own size supplies the UVs
// (SetPacketTexture reads them from DG_TEX), so the quad cannot crop, and
// shifting it up 11 rows would drag the backdrop's top edge onto the ramp's
// brighter 16 band - a worse notch than the one being fixed.
// Kept as a plain array in .rdata, not an initializer_list: the bytes are
// then greppable in the shipped binary, which is how this build verified
// that what ends up on disc is what the tools produced.
static inline const unsigned char MGS1_BrightnessTextData[426] = {
0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0x40, 0xDB, 0x00, 0x10, 0xDF, 0x00,
0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00, 0xBF, 0xDB, 0xFF, 0xC1, 0xEF, 0x00,
0xDC, 0x00, 0x10, 0xDF, 0x00, 0xDC, 0xFF, 0xC1, 0xEF, 0x00, 0xDC, 0xFF,
0xC1, 0xEF, 0x00, 0xDC, 0x00, 0x10, 0xDF, 0x00, 0xDC, 0xFF, 0xC1, 0xEF,
0x00, 0xDC, 0xFF, 0xC1, 0xEF, 0x00,
};

// Where those bytes go: row 46 of the payload, i.e. file offset 55503496,
// mapped through (lba + fo / 2048) * 2352 + 24 + fo % 2048. Both writes
// land inside one sector, so neither needs splitting.
static std::vector<Ketchup_DiskPatch> MGS1_BrightnessTextPatches()
{
std::vector<unsigned char> data(std::begin(MGS1_BrightnessTextData),
std::end(MGS1_BrightnessTextData));
return {
{981, "USA", 0, 0x165A4790ull, data, "sc_text rows 46-69 blanked"},
{981, "USA", 1, 0x11EE3E40ull, data, "sc_text rows 46-69 blanked"},
};
}

std::vector<Ketchup_DiskPatch> MGS1_KetchupPatches = {};

const std::vector<unsigned char> MGS1_DataBlacklist_Medicine = {
0, 152, 0, 72, 152, 72, 152, 152, 152
};
Expand Down
26 changes: 26 additions & 0 deletions src/m2fix/ketchup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,36 @@ std::filesystem::path Ketchup<Q>::RootPath(Ketchup_TitleInfo &title, Ketchup_Ver
return root;
}

template <Squirk Q>
bool Ketchup<Q>::ProcessBuiltins(HSQUIRRELVM<Q> v, Ketchup_TitleInfo &title, Ketchup_VersionInfo &version, Ketchup_DiskInfo &disk)
{
auto *patches = M2Fix::GameInstance().SQKetchupPatches();
if (!patches) return true;

for (auto &patch : *patches) {
if (patch.title != title.id) continue;
if (patch.version != version.name) continue;
if (patch.disk != disk.id) continue;
if (patch.data.empty()) continue;

spdlog::info("[SQ] [Ketchup] built-in patch {} -> {} {} disk {}.",
patch.name, title.name, version.name, disk.id);
WriteSource = static_cast<unsigned int>(WriteSources.size());
WriteSources.push_back("built-in " + patch.name);
ApplyBlock(v, title, version, disk, patch.offset,
patch.data.data(), patch.data.size());
Comment on lines +338 to +339

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bypass the range blacklist for the built-in fix

In the default BrightnessText=fixed mode, both built-in offsets are inside the ranges registered by SetPatchRangeBlacklist. Calling ApplyBlock here routes the built-in data through the hooked entryCdRomPatch, where SQNative_entryCdRomPatch matches that same range and returns 1, suppressing the native write. Consequently the collection replacement is removed but the blank-row patch is also discarded, so fixed behaves like original instead of producing the advertised four-line texture.

Useful? React with 👍 / 👎.

}

return true;
}

template <Squirk Q>
bool Ketchup<Q>::ProcessDisk(HSQUIRRELVM<Q> v, Ketchup_TitleInfo &title, Ketchup_VersionInfo &version, Ketchup_DiskInfo &disk)
{
// Built-ins first, and unconditionally: they do not live in the mods folder,
// so an absent or empty one must not skip them.
ProcessBuiltins(v, title, version, disk);

std::filesystem::directory_entry root { RootPath(title, version, disk) };
spdlog::info("[SQ] [Ketchup] base path is {}.", root.path().string());

Expand Down
13 changes: 13 additions & 0 deletions src/m2fix/ketchup.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ typedef struct {



// A patch this fix carries itself, rather than reading from a PPF in the mods
// folder. Same destination as a PPF record - an offset into the disc image -
// but the game keys it, so one entry targets exactly one release and disk.
typedef struct {
unsigned int title;
std::string version;
unsigned int disk;
uint64_t offset;
std::vector<unsigned char> data;
std::string name;
} Ketchup_DiskPatch;

// One write as it was applied, kept only until the folder has been processed
// so collisions between two patches can be reported. See ReportOverlaps().
typedef struct {
Expand Down Expand Up @@ -64,6 +76,7 @@ class Ketchup
static bool ApplyPPF3(HSQUIRRELVM<Q> v, Ketchup_TitleInfo &title, Ketchup_VersionInfo &version, Ketchup_DiskInfo &disk, std::ifstream &data);
static int MetaPPF_FileId(std::ifstream &data, int version);

static bool ProcessBuiltins(HSQUIRRELVM<Q> v, Ketchup_TitleInfo &title, Ketchup_VersionInfo &version, Ketchup_DiskInfo &disk);
static bool ProcessDisk(HSQUIRRELVM<Q> v, Ketchup_TitleInfo &title, Ketchup_VersionInfo &version, Ketchup_DiskInfo &disk);
static bool ProcessVersion(HSQUIRRELVM<Q> v, Ketchup_TitleInfo &title, Ketchup_VersionInfo &version);
static bool ProcessTitle(HSQUIRRELVM<Q> v, Ketchup_TitleInfo &title);
Expand Down
17 changes: 17 additions & 0 deletions src/m2fix/m2config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,20 @@ void M2Config::Load()
inipp::get_value(ini.sections["Patches"], "EnableMosaic", bPatchesEnableMosaic);
inipp::get_value(ini.sections["Patches"], "RestoreGhosts", bPatchesRestoreGhosts);
inipp::get_value(ini.sections["Patches"], "RestoreMedicine", bPatchesRestoreMedicine);
{
// Tri-state, and it accepts a bool for anyone who assumes one: `true`
// is the game's own text, which is what the old RestoreBrightnessText
// meant, and `false` leaves the collection's version alone.
std::string mode;
if (inipp::get_value(ini.sections["Patches"], "BrightnessText", mode)) {
for (auto &c : mode) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
if (mode == "fixed") eBrightnessText = M2BrightnessText::Fixed;
else if (mode == "original" || mode == "true") eBrightnessText = M2BrightnessText::Original;
else if (mode == "collection" || mode == "false") eBrightnessText = M2BrightnessText::Collection;
else spdlog::warn("[Config] BrightnessText: '{}' is not one of fixed / original /"
" collection, keeping the default.", mode);
}
}
inipp::get_value(ini.sections["Patches"], "PreserveConfiguration", bPatchesPreserveConfiguration);
inipp::get_value(ini.sections["Game"], "StageSelect", bGameStageSelect);
inipp::get_value(ini.sections["Game"], "EnglishText", bGameEnglishText);
Expand Down Expand Up @@ -174,6 +188,9 @@ void M2Config::Load()
spdlog::info("[Config] bPatchesEnableMosaic: {}", bPatchesEnableMosaic);
spdlog::info("[Config] bPatchesRestoreGhosts: {}", bPatchesRestoreGhosts);
spdlog::info("[Config] bPatchesRestoreMedicine: {}", bPatchesRestoreMedicine);
spdlog::info("[Config] eBrightnessText: {}",
eBrightnessText == M2BrightnessText::Fixed ? "fixed" :
eBrightnessText == M2BrightnessText::Original ? "original" : "collection");
spdlog::info("[Config] bPatchesPreserveConfiguration: {}", bPatchesPreserveConfiguration);
spdlog::info("[Config] bGameStageSelect: {}", bGameStageSelect);
spdlog::info("[Config] bGameEnglishText: {}", bGameEnglishText);
Expand Down
14 changes: 14 additions & 0 deletions src/m2fix/m2config.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@

#include "m2fixbase.h"

// What to do about the collection's replacement for MGS1's Option -> SCREEN
// help texture. See [Patches] BrightnessText in the ini.
enum class M2BrightnessText

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but I question the location of this

{
Collection, // leave it alone
Fixed, // its four lines, at the position the game draws them
Original, // the game's own six lines, at that same position
};

class M2Config : public M2FixBase
{
public:
Expand All @@ -13,6 +22,7 @@ class M2Config : public M2FixBase
bPatchesEnableMosaic = true;
bPatchesRestoreGhosts = true;
bPatchesRestoreMedicine = true;
eBrightnessText = M2BrightnessText::Fixed;
bPatchesPreserveConfiguration = true;
}

Expand Down Expand Up @@ -67,6 +77,10 @@ class M2Config : public M2FixBase
static inline bool bPatchesEnableMosaic;
static inline bool bPatchesRestoreGhosts;
static inline bool bPatchesRestoreMedicine;
// Given a value here, not just in the constructor: an enum's zero is
// Collection, so a read before the singleton is first constructed would
// otherwise see the wrong default.
static inline M2BrightnessText eBrightnessText = M2BrightnessText::Fixed;
static inline bool bPatchesPreserveConfiguration;
static inline bool bShouldCheckForUpdates;
static inline bool bConsoleUpdateNotifications;
Expand Down
3 changes: 3 additions & 0 deletions src/m2fix/m2game.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class M2Game : public M2FixBase
}

virtual std::vector<Ketchup_TitleInfo> *SQKetchupHook() { return nullptr; }
// Patches the fix writes to the disc image itself, alongside the mods
// folder's PPFs. Keyed by title, version and disk; see Ketchup_DiskPatch.
virtual std::vector<Ketchup_DiskPatch> *SQKetchupPatches() { return nullptr; }

virtual void SQOnMemoryDefine() {}
virtual void SQOnUpdateGadgets() {}
Expand Down
10 changes: 10 additions & 0 deletions src/m2fix/patch_range.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once
#include <cstdint>

struct PatchRange {
unsigned title, disk;
uint64_t start, end;
bool matches(unsigned currentTitle, unsigned currentDisk, uint64_t offset) const {
return title == currentTitle && disk == currentDisk && offset >= start && offset < end;
}
};
13 changes: 13 additions & 0 deletions src/modules/sqhook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ void SQHook<Q>::SetNativeCallHook(const char *name, SQFUNCTION<Q> func)
NativeTable.push_back({ name, func });
}

template <Squirk Q>
void SQHook<Q>::SetPatchRangeBlacklist(unsigned title, unsigned disk, uint64_t start, uint64_t end)
{
RangeBlacklist.push_back({ title, disk, start, end });
}


template <Squirk Q>
Expand Down Expand Up @@ -636,6 +641,14 @@ SQInteger SQHook<Q>::SQNative_entryCdRomPatch(HSQUIRRELVM<Q> v)
return 1;
}
}
for (auto &range : RangeBlacklist)
{
if (!range.matches(SQGlobals<Q>::GetTitle(), SQGlobals<Q>::GetDisk(), offset)) continue;
spdlog::info("[SQ] [Patch] filtering CD-ROM patch {} at offset 0x{:x},"
" inside blacklisted range 0x{:x}..0x{:x}.",
file.empty() ? "<data>" : file, offset, range.start, range.end);
return 1;
}

return 0;
}
Expand Down
7 changes: 7 additions & 0 deletions src/modules/sqhook.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "m2fixbase.h"
#include "../m2fix/patch_range.h"

#include "sqhelper.h"

Expand Down Expand Up @@ -57,6 +58,7 @@ class SQHook: public M2FixBase
static void SetLoadScriptHook(const char *name, SQFUNCTION<Q> func);
static void SetNativeCallHook(const char *name, SQFUNCTION<Q> func);
static void SetPatchFileBlacklist(std::string file);
static void SetPatchRangeBlacklist(unsigned title, unsigned disk, uint64_t start, uint64_t end);
static void SetPatchDataBlacklist(std::vector<unsigned char> data);
static void SetTextureWhitelist(unsigned int data);

Expand Down Expand Up @@ -139,6 +141,11 @@ class SQHook: public M2FixBase
private:
static inline HSQREMOTEDBG<Q> DBG = nullptr;
static inline std::vector<std::string> FileBlacklist = {};
// Half-open disc-image ranges. Filters by where a patch writes rather than
// what its file is called, which is what you want for an asset whose patch
// names differ per disk - the collection names each piece after its own
// image offset, so one range covers every piece of one archive entry.
static inline std::vector<PatchRange> RangeBlacklist = {};
static inline std::vector<std::vector<unsigned char>> DataBlacklist = {};
static inline std::vector<unsigned int> TextureWhitelist = {};
static inline unsigned int ThreadCount = 0;
Expand Down
11 changes: 11 additions & 0 deletions tests/test_patch_range.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "../src/m2fix/patch_range.h"
#include <stdexcept>
#include <iostream>
int main() {
PatchRange range{981, 1, 100, 200};
if (!range.matches(981, 1, 100) || !range.matches(981, 1, 199) ||
range.matches(99, 1, 150) || range.matches(981, 0, 150) ||
range.matches(981, 1, 200))
throw std::runtime_error("Title/disc/range boundary mismatch");
std::cout << "Patch range tests passed\n";
}
Loading