Limit corrupted battle save payloads
This commit is contained in:
@@ -77,6 +77,10 @@ type BattleSaveValidationOptions = {
|
||||
};
|
||||
|
||||
const unitDirections = new Set<UnitDirection>(['south', 'east', 'north', 'west']);
|
||||
const maxBattleLogEntries = 10;
|
||||
const maxBattleLogEntryLength = 96;
|
||||
const defaultBattleSaveArrayLimit = 128;
|
||||
const maxStatusKindsPerUnit = 2;
|
||||
|
||||
export function normalizeBattleSaveSlot(slot: number, slotCount: number) {
|
||||
const safeSlot = Number.isFinite(slot) ? Math.floor(slot) : 1;
|
||||
@@ -174,7 +178,12 @@ function isStringArray(value: unknown) {
|
||||
}
|
||||
|
||||
function isUnitIdArray(value: unknown, options: BattleSaveValidationOptions) {
|
||||
return Array.isArray(value) && value.every((entry) => typeof entry === 'string' && isKnownUnitId(entry, options));
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.length <= unitReferenceLimit(options) &&
|
||||
hasUniqueStrings(value) &&
|
||||
value.every((entry) => typeof entry === 'string' && isKnownUnitId(entry, options))
|
||||
);
|
||||
}
|
||||
|
||||
function isOptionalStringArray(value: unknown) {
|
||||
@@ -182,12 +191,18 @@ function isOptionalStringArray(value: unknown) {
|
||||
}
|
||||
|
||||
function isBattleLog(value: unknown) {
|
||||
return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.length <= maxBattleLogEntries &&
|
||||
value.every((entry) => typeof entry === 'string' && entry.length <= maxBattleLogEntryLength)
|
||||
);
|
||||
}
|
||||
|
||||
function isAttackIntentArray(value: unknown, options: BattleSaveValidationOptions) {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.length <= unitReferenceLimit(options) &&
|
||||
hasUniqueRecordStrings(value, 'attackerId') &&
|
||||
value.every(
|
||||
(intent) =>
|
||||
isRecord(intent) &&
|
||||
@@ -277,10 +292,15 @@ function isOptionalItemStockRecord(value: unknown, options: BattleSaveValidation
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Object.keys(value).length > unitReferenceLimit(options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.entries(value).every(
|
||||
([unitId, stocks]) =>
|
||||
isKnownUnitId(unitId, options) &&
|
||||
isRecord(stocks) &&
|
||||
Object.keys(stocks).length <= itemReferenceLimit(options) &&
|
||||
Object.entries(stocks).every(
|
||||
([itemId, count]) =>
|
||||
(!options.validItemIds || options.validItemIds.has(itemId)) && Number.isInteger(count) && Number(count) >= 0
|
||||
@@ -292,6 +312,8 @@ function isOptionalBuffArray(value: unknown, options: BattleSaveValidationOption
|
||||
return (
|
||||
value === undefined ||
|
||||
(Array.isArray(value) &&
|
||||
value.length <= unitReferenceLimit(options) &&
|
||||
hasUniqueRecordStrings(value, 'unitId') &&
|
||||
value.every(
|
||||
(buff) =>
|
||||
isRecord(buff) &&
|
||||
@@ -310,6 +332,8 @@ function isOptionalStatusArray(value: unknown, options: BattleSaveValidationOpti
|
||||
return (
|
||||
value === undefined ||
|
||||
(Array.isArray(value) &&
|
||||
value.length <= unitReferenceLimit(options) * maxStatusKindsPerUnit &&
|
||||
hasUniqueStatusKeys(value) &&
|
||||
value.every(
|
||||
(status) =>
|
||||
isRecord(status) &&
|
||||
@@ -332,6 +356,10 @@ function isOptionalStatsRecord(value: unknown, options: BattleSaveValidationOpti
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Object.keys(value).length > unitReferenceLimit(options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.entries(value).every(
|
||||
([unitId, stats]) =>
|
||||
isKnownUnitId(unitId, options) &&
|
||||
@@ -345,7 +373,13 @@ function isOptionalStatsRecord(value: unknown, options: BattleSaveValidationOpti
|
||||
}
|
||||
|
||||
function isOptionalEnemyUsableUseKeyArray(value: unknown, battleId: string, options: BattleSaveValidationOptions) {
|
||||
return value === undefined || (Array.isArray(value) && value.every((entry) => isEnemyUsableUseKey(entry, battleId, options)));
|
||||
return (
|
||||
value === undefined ||
|
||||
(Array.isArray(value) &&
|
||||
value.length <= enemyUsableUseKeyLimit(options) &&
|
||||
hasUniqueStrings(value) &&
|
||||
value.every((entry) => isEnemyUsableUseKey(entry, battleId, options)))
|
||||
);
|
||||
}
|
||||
|
||||
function isEnemyUsableUseKey(value: unknown, battleId: string, options: BattleSaveValidationOptions) {
|
||||
@@ -369,3 +403,35 @@ function isEnemyUsableUseKey(value: unknown, battleId: string, options: BattleSa
|
||||
function isKnownUnitId(unitId: string, options: BattleSaveValidationOptions) {
|
||||
return !options.validUnitIds || options.validUnitIds.has(unitId);
|
||||
}
|
||||
|
||||
function unitReferenceLimit(options: BattleSaveValidationOptions) {
|
||||
return options.validUnitIds?.size ?? defaultBattleSaveArrayLimit;
|
||||
}
|
||||
|
||||
function itemReferenceLimit(options: BattleSaveValidationOptions) {
|
||||
return options.validItemIds?.size ?? defaultBattleSaveArrayLimit;
|
||||
}
|
||||
|
||||
function enemyUsableUseKeyLimit(options: BattleSaveValidationOptions) {
|
||||
if (options.validUnitIds && options.validUsableIds) {
|
||||
return options.validUnitIds.size * options.validUsableIds.size;
|
||||
}
|
||||
|
||||
return defaultBattleSaveArrayLimit;
|
||||
}
|
||||
|
||||
function hasUniqueStrings(values: unknown[]) {
|
||||
return new Set(values.filter((entry) => typeof entry === 'string')).size === values.length;
|
||||
}
|
||||
|
||||
function hasUniqueRecordStrings(values: unknown[], field: string) {
|
||||
const strings = values.map((value) => (isRecord(value) && typeof value[field] === 'string' ? value[field] : undefined));
|
||||
return strings.every((value) => value !== undefined) && new Set(strings).size === strings.length;
|
||||
}
|
||||
|
||||
function hasUniqueStatusKeys(values: unknown[]) {
|
||||
const keys = values.map((value) =>
|
||||
isRecord(value) && typeof value.unitId === 'string' && typeof value.kind === 'string' ? `${value.unitId}:${value.kind}` : undefined
|
||||
);
|
||||
return keys.every((key) => key !== undefined) && new Set(keys).size === keys.length;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user