mirror of
https://github.com/Shadowss/TravianZ.git
synced 2026-08-13 23:01:02 +00:00
Fix some atomic exploit
Fix some atomic exploit
This commit is contained in:
@@ -347,6 +347,130 @@ trait AutomationBattleResolution {
|
||||
|
||||
return (mysqli_affected_rows($database->dblink) === 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim atomic pentru un retur de trupe.
|
||||
*
|
||||
* Un attack ref poate avea legitim un singur movement de retur
|
||||
* (sort_type = 4).
|
||||
*
|
||||
* Daca exista deja un alt return pentru acelasi ref care a fost
|
||||
* procesat, acest movement este considerat duplicat si este marcat
|
||||
* processed fara a mai credita trupele.
|
||||
*/
|
||||
|
||||
private function claimReturnMovementRecord($moveid, $ref) {
|
||||
global $database;
|
||||
|
||||
$moveid = (int)$moveid;
|
||||
$ref = (int)$ref;
|
||||
|
||||
if ($moveid <= 0 || $ref <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Lock pe ATTACK REF, nu pe moveid.
|
||||
*
|
||||
* Astfel doua movement-uri duplicate cu moveid diferit
|
||||
* nu pot trece simultan de verificarea de unicitate.
|
||||
*/
|
||||
$lockResult = mysqli_query(
|
||||
$database->dblink,
|
||||
"SELECT GET_LOCK('return_attack_ref_$ref', 10) AS locked"
|
||||
);
|
||||
|
||||
if (!$lockResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lockRow = mysqli_fetch_assoc($lockResult);
|
||||
|
||||
if (
|
||||
!isset($lockRow['locked']) ||
|
||||
(int)$lockRow['locked'] !== 1
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
/*
|
||||
* Verificam daca acest movement mai este pending.
|
||||
*/
|
||||
$q = "
|
||||
SELECT moveid
|
||||
FROM " . TB_PREFIX . "movement
|
||||
WHERE moveid = $moveid
|
||||
AND sort_type = 4
|
||||
AND proc = 0
|
||||
LIMIT 1
|
||||
";
|
||||
|
||||
$result = mysqli_query(
|
||||
$database->dblink,
|
||||
$q
|
||||
);
|
||||
|
||||
if (!$result || mysqli_num_rows($result) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verificam daca un alt return pentru acelasi attack
|
||||
* a fost deja procesat.
|
||||
*/
|
||||
$q = "
|
||||
SELECT moveid
|
||||
FROM " . TB_PREFIX . "movement
|
||||
WHERE ref = $ref
|
||||
AND sort_type = 4
|
||||
AND proc = 1
|
||||
AND moveid <> $moveid
|
||||
LIMIT 1
|
||||
";
|
||||
|
||||
$result = mysqli_query(
|
||||
$database->dblink,
|
||||
$q
|
||||
);
|
||||
|
||||
if ($result && mysqli_num_rows($result) > 0) {
|
||||
/*
|
||||
* Este un duplicate.
|
||||
*
|
||||
* Il consumam fara sa adaugam trupele.
|
||||
*/
|
||||
mysqli_query(
|
||||
$database->dblink,
|
||||
"UPDATE " . TB_PREFIX . "movement
|
||||
SET proc = 1
|
||||
WHERE moveid = $moveid
|
||||
AND proc = 0"
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Claim atomic pentru movement-ul legitim.
|
||||
*/
|
||||
$result = mysqli_query(
|
||||
$database->dblink,
|
||||
"UPDATE " . TB_PREFIX . "movement
|
||||
SET proc = 1
|
||||
WHERE moveid = $moveid
|
||||
AND proc = 0"
|
||||
);
|
||||
|
||||
return $result && mysqli_affected_rows($database->dblink) === 1;
|
||||
|
||||
} finally {
|
||||
mysqli_query(
|
||||
$database->dblink,
|
||||
"SELECT RELEASE_LOCK('return_attack_ref_$ref')"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle hero evasion: if the defender has evasion active and can afford it,
|
||||
|
||||
@@ -211,7 +211,7 @@ trait AutomationTroopMovements {
|
||||
$time = time();
|
||||
$q = "
|
||||
SELECT
|
||||
`to`, `from`, moveid, starttime, endtime, wood, clay, iron, crop,
|
||||
`to`, `from`, moveid, ref, starttime, endtime, wood, clay, iron, crop,
|
||||
t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11
|
||||
FROM
|
||||
".TB_PREFIX."movement,
|
||||
@@ -238,9 +238,11 @@ trait AutomationTroopMovements {
|
||||
$database->getOasisEnforce($vilIDs, 1);
|
||||
|
||||
foreach($dataarray as $data) {
|
||||
if (!$this->claimMovementRecord($data['moveid'])) {
|
||||
continue;
|
||||
}
|
||||
if (!$this->claimReturnMovementRecord(
|
||||
$data['moveid'],
|
||||
$data['ref'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tribe = $database->getUserField($database->getVillageField($data['to'], "owner"), "tribe", 0);
|
||||
$u = $tribe == 1 ? "" : $tribe - 1;
|
||||
|
||||
@@ -192,6 +192,52 @@ trait DatabaseMovementQueries {
|
||||
return (mysqli_affected_rows($this->dblink) === 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revendicare ATOMICA a unei miscari care urmeaza sa fie anulata din rally point
|
||||
* (build.php?mode=troops&cancel=1&moveid=X).
|
||||
*
|
||||
* DE CE EXISTA: varianta veche din build.php facea, in trei pasi separati:
|
||||
* SELECT COUNT(*) ... WHERE proc = 0 AND moveid = X -> daca Total == 1
|
||||
* UPDATE ... SET proc = 1 WHERE proc = 0 AND moveid = X
|
||||
* addMovement(4, to, from, REF-UL VECHI, now, end)
|
||||
* Doua request-uri simultane treceau AMANDOUA de SELECT COUNT (ambele vad Total = 1),
|
||||
* UPDATE-ul era idempotent, deci amandoua ajungeau la addMovement() si inserau DOUA
|
||||
* randuri sort_type = 4 cu ACELASI ref. Cron-ul (AutomationTroopMovements) livreaza
|
||||
* trupele o data PER RAND DE MOVEMENT, nu per ref => trupele se dublau la fiecare
|
||||
* ciclu trimite-anuleaza: 30k -> 60k -> 120k -> 242k -> ...
|
||||
*
|
||||
* Acum proprietatea, tipul, fereastra de 90 de secunde si proc = 0 sunt toate in
|
||||
* acelasi UPDATE conditionat. Doar request-ul care chiar modifica randul primeste
|
||||
* true (affected_rows === 1); toate celelalte pierd cursa si nu creeaza nimic.
|
||||
*
|
||||
* ANOMALIE SEMNALATA, NU SCHIMBATA TACIT: codul vechi nu verifica deloc sort_type,
|
||||
* deci se putea "anula" si un sort_type = 4 (retur) al carui `from` era satul tau -
|
||||
* adica returul unei intariri straine care pleaca din satul tau - iar returul nou
|
||||
* generat aducea acele trupe LA TINE. Restrictia sort_type IN (3, 5) inchide si
|
||||
* acest vector si pastreaza exact cele doua cazuri legitime din 16_walking.tpl:
|
||||
* atac/intarire iesita din sat (3) si colonisti (5).
|
||||
*
|
||||
* @param int $moveid ID-ul miscarii
|
||||
* @param int $wid Satul curent (trebuie sa fie expeditorul)
|
||||
* @return bool True doar pentru request-ul care a revendicat miscarea
|
||||
*/
|
||||
function claimMovementCancel($moveid, $wid) {
|
||||
$moveid = (int) $moveid;
|
||||
$wid = (int) $wid;
|
||||
|
||||
if ($moveid <= 0 || $wid <= 0) return false;
|
||||
|
||||
$limit = time() - 90;
|
||||
|
||||
$q = "UPDATE " . TB_PREFIX . "movement SET proc = 1 WHERE moveid = $moveid AND proc = 0 AND `from` = $wid AND sort_type IN (3, 5) AND starttime > $limit";
|
||||
|
||||
if (!mysqli_query($this->dblink, $q)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (mysqli_affected_rows($this->dblink) === 1);
|
||||
}
|
||||
|
||||
// no need to cache this method
|
||||
function getA2b($ckey) {
|
||||
list($ckey) = $this->escape_input($ckey);
|
||||
@@ -740,4 +786,4 @@ trait DatabaseMovementQueries {
|
||||
|
||||
self::$prisonersCache = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,79 @@ trait DatabaseTroopQueries {
|
||||
$id = (int) $id;
|
||||
mysqli_query($this->dblink, "SELECT RELEASE_LOCK('enforce_$id')");
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock pentru procesarea returului trupelor dintr-o oaza.
|
||||
*
|
||||
* Este separat de enforce_$id deoarece o oaza poate avea mai multe
|
||||
* randuri de reinforcement.
|
||||
*
|
||||
* Scop:
|
||||
* Request A -> citeste reinforcement din oasis
|
||||
* Request B -> citeste acelasi reinforcement
|
||||
* => fara lock, ambele pot crea movement de retur.
|
||||
*/
|
||||
function getOasisReturnLock($wref) {
|
||||
$wref = (int) $wref;
|
||||
|
||||
$result = mysqli_query(
|
||||
$this->dblink,
|
||||
"SELECT GET_LOCK('oasis_return_$wref', 10) AS locked"
|
||||
);
|
||||
|
||||
if (!$result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$row = mysqli_fetch_assoc($result);
|
||||
|
||||
return isset($row['locked']) && (int)$row['locked'] === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Elibereaza lock-ul pentru returul unei oaze.
|
||||
*/
|
||||
function releaseOasisReturnLock($wref) {
|
||||
$wref = (int) $wref;
|
||||
|
||||
mysqli_query(
|
||||
$this->dblink,
|
||||
"SELECT RELEASE_LOCK('oasis_return_$wref')"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Citeste DIRECT din DB toate reinforcement-urile aflate in oaza.
|
||||
*
|
||||
* IMPORTANT:
|
||||
* Nu folosim cache aici. Functia este apelata DUPA obtinerea lock-ului
|
||||
* pentru a preveni TOCTOU/race condition.
|
||||
*/
|
||||
function getOasisEnforceByWref($wref, $use_cache = false) {
|
||||
$wref = (int) $wref;
|
||||
|
||||
if ($wref <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$q = "
|
||||
SELECT e.*, o.conqured
|
||||
FROM " . TB_PREFIX . "enforcement AS e
|
||||
LEFT JOIN " . TB_PREFIX . "odata AS o
|
||||
ON e.vref = o.wref
|
||||
WHERE e.vref = $wref
|
||||
AND o.wref = $wref
|
||||
AND o.conqured > 0
|
||||
";
|
||||
|
||||
$result = mysqli_query($this->dblink, $q);
|
||||
|
||||
if (!$result) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->mysqli_fetch_all($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the unit table(s) and troops if presents
|
||||
|
||||
+371
-38
@@ -351,35 +351,353 @@ class Units {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returneaza trupele stationate in sat si/sau in oazele acestuia.
|
||||
*
|
||||
* IMPORTANT:
|
||||
* - fiecare reinforcement este procesat sub lock;
|
||||
* - fiecare oaza este procesata sub un lock separat;
|
||||
* - datele sunt re-citite din DB dupa obtinerea lock-ului;
|
||||
* - nu folosim cache pentru datele critice.
|
||||
*
|
||||
* @param int $wref
|
||||
* @param int $mode
|
||||
*
|
||||
* mode = 0:
|
||||
* returneaza reinforcement-urile din sat + oazele lui
|
||||
*
|
||||
* mode = 1:
|
||||
* returneaza doar reinforcement-urile din oazele satului
|
||||
*/
|
||||
public function returnTroops($wref, $mode = 0) {
|
||||
global $database;
|
||||
|
||||
if(!$mode){
|
||||
$getenforce = $database->getEnforceVillage($wref, 0);
|
||||
foreach($getenforce as $enforce) $this->processReturnTroops($enforce);
|
||||
}
|
||||
|
||||
// check oasis
|
||||
$getenforce1 = $database->getOasisEnforce($wref, 1);
|
||||
foreach($getenforce1 as $enforce) $this->processReturnTroops($enforce);
|
||||
|
||||
// set oasis to default
|
||||
if(count($getenforce1) > 0) $database->regenerateOasisUnits($getenforce1[0]['vref']);
|
||||
|
||||
$wref = (int) $wref;
|
||||
|
||||
if ($wref <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reinforcement-uri stationate direct in sat.
|
||||
*
|
||||
* Le procesam individual sub enforce lock.
|
||||
*/
|
||||
if (!$mode) {
|
||||
$getenforce = $database->getEnforceVillage($wref, 0, false);
|
||||
|
||||
if ($getenforce && count($getenforce)) {
|
||||
foreach ($getenforce as $enforce) {
|
||||
if (!empty($enforce['id'])) {
|
||||
$this->returnEnforcementRecord((int)$enforce['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* OAZE
|
||||
*
|
||||
* getOasisEnforce() ne da toate reinforcement-urile din oazele
|
||||
* cucerite de acest sat.
|
||||
*
|
||||
* Nu procesam direct rezultatul deoarece acesta poate fi cached.
|
||||
* Extragem doar ID-urile oazelor si apoi fiecare oaza este re-citita
|
||||
* dupa obtinerea lock-ului.
|
||||
*/
|
||||
$getenforce1 = $database->getOasisEnforce($wref, 1, false);
|
||||
|
||||
if (!$getenforce1 || !count($getenforce1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$oasisRefs = [];
|
||||
|
||||
foreach ($getenforce1 as $enforce) {
|
||||
$oasisWref = isset($enforce['vref']) ? (int)$enforce['vref'] : 0;
|
||||
|
||||
if ($oasisWref > 0) {
|
||||
$oasisRefs[$oasisWref] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_keys($oasisRefs) as $oasisWref) {
|
||||
$this->returnOasisTroops($oasisWref);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returneaza TOATE reinforcement-urile dintr-o singura oaza.
|
||||
*
|
||||
* Aceasta este protectia principala impotriva exploitului de duplicare.
|
||||
*
|
||||
* Doua request-uri simultane pentru aceeasi oaza:
|
||||
*
|
||||
* Request A -> obtine lock
|
||||
* Request B -> asteapta
|
||||
*
|
||||
* A -> citeste reinforcement
|
||||
* A -> creeaza movement
|
||||
* A -> sterge reinforcement
|
||||
* A -> release lock
|
||||
*
|
||||
* B -> obtine lock
|
||||
* B -> re-citeste DB
|
||||
* B -> nu mai gaseste reinforcement
|
||||
* B -> nu mai poate duplica nimic
|
||||
*/
|
||||
public function returnOasisTroops($oasisWref) {
|
||||
global $database;
|
||||
|
||||
$oasisWref = (int)$oasisWref;
|
||||
|
||||
if ($oasisWref <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$database->getOasisReturnLock($oasisWref)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
/*
|
||||
* IMPORTANT:
|
||||
* Re-fetch DIRECT din DB dupa lock.
|
||||
* Nu folosim getOasisEnforce(..., cache).
|
||||
*/
|
||||
$reinforcements = $database->getOasisEnforceByWref(
|
||||
$oasisWref,
|
||||
false
|
||||
);
|
||||
|
||||
if (!$reinforcements || !count($reinforcements)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($reinforcements as $enforce) {
|
||||
if (empty($enforce['id'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* processReturnTroops() sterge reinforcement-ul dupa ce
|
||||
* creeaza movement-ul de retur.
|
||||
*/
|
||||
$this->processReturnTroops($enforce);
|
||||
}
|
||||
|
||||
/*
|
||||
* Comportamentul original:
|
||||
* dupa ce reinforcement-urile au fost returnate, oaza isi
|
||||
* regenereaza trupele naturale.
|
||||
*/
|
||||
$database->regenerateOasisUnits($oasisWref);
|
||||
|
||||
return true;
|
||||
|
||||
} finally {
|
||||
$database->releaseOasisReturnLock($oasisWref);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returneaza un singur reinforcement record.
|
||||
*
|
||||
* Protectie suplimentara pentru cazurile in care un sat este sters
|
||||
* sau reinforcement-ul este procesat dintr-o alta cale.
|
||||
*/
|
||||
private function returnEnforcementRecord($enforceId) {
|
||||
global $database;
|
||||
|
||||
$enforceId = (int)$enforceId;
|
||||
|
||||
if ($enforceId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$database->getEnforceLock($enforceId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
/*
|
||||
* Re-fetch dupa lock.
|
||||
*/
|
||||
$enforce = $database->getEnforceArray(
|
||||
$enforceId,
|
||||
0,
|
||||
false
|
||||
);
|
||||
|
||||
/*
|
||||
* Poate sa fi fost deja procesat de alt request.
|
||||
*/
|
||||
if (!$enforce || empty($enforce['id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->processReturnTroops($enforce);
|
||||
|
||||
return true;
|
||||
|
||||
} finally {
|
||||
$database->releaseEnforceLock($enforceId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creeaza movement-ul de retur pentru un reinforcement.
|
||||
*
|
||||
* ATENTIE:
|
||||
* Aceasta functie NU mai este responsabila de locking.
|
||||
* Lock-ul este facut de:
|
||||
*
|
||||
* returnEnforcementRecord()
|
||||
* returnOasisTroops()
|
||||
*
|
||||
* astfel incat sa avem o singura responsabilitate pentru lock.
|
||||
*/
|
||||
private function processReturnTroops($enforce) {
|
||||
global $database;
|
||||
|
||||
$to = $database->getVillage($enforce['from']);
|
||||
$tribe = $database->getUserField($to['owner'], 'tribe', 0);
|
||||
|
||||
if (empty($enforce['id']) || empty($enforce['from'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fromWref = (int)$enforce['from'];
|
||||
$oasisWref = (int)$enforce['vref'];
|
||||
|
||||
/*
|
||||
* Satul de origine al reinforcement-ului.
|
||||
*/
|
||||
$to = $database->getVillage($fromWref);
|
||||
|
||||
if (!$to || empty($to['owner'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tribe = (int)$database->getUserField(
|
||||
$to['owner'],
|
||||
'tribe',
|
||||
0
|
||||
);
|
||||
|
||||
if ($tribe <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$start = ($tribe - 1) * 10 + 1;
|
||||
|
||||
$troopsTime = $this->getWalkingTroopsTime($enforce['from'], $enforce['vref'], $to['owner'], $tribe, $enforce, 1);
|
||||
$time = $database->getArtifactsValueInfluence($from['owner'], $enforce['from'], 2, $troopsTime);
|
||||
|
||||
$reference = $database->addAttack($enforce['from'], $enforce['u'.$start], $enforce['u'.($start + 1)], $enforce['u'.($start + 2)], $enforce['u'.($start + 3)], $enforce['u'.($start + 4)], $enforce['u'.($start + 5)], $enforce['u'.($start + 6)], $enforce['u'.($start + 7)], $enforce['u'.($start + 8)], $enforce['u'.($start + 9)], $enforce['hero'], 2, 0, 0, 0, 0);
|
||||
$database->addMovement(4, $enforce['vref'], $enforce['from'], $reference, time(), ($time + time()));
|
||||
$database->deleteReinf($enforce['id']);
|
||||
|
||||
/*
|
||||
* Calculeaza timpul de mers Oaza -> Sat.
|
||||
*/
|
||||
$troopsTime = $this->getWalkingTroopsTime(
|
||||
$fromWref,
|
||||
$oasisWref,
|
||||
$to['owner'],
|
||||
$tribe,
|
||||
$enforce,
|
||||
1
|
||||
);
|
||||
|
||||
/*
|
||||
* BUG FIX:
|
||||
*
|
||||
* Codul vechi folosea:
|
||||
*
|
||||
* $from['owner']
|
||||
*
|
||||
* dar $from nu exista in aceasta functie.
|
||||
*
|
||||
* Proprietarul corect este $to['owner'].
|
||||
*/
|
||||
$time = $database->getArtifactsValueInfluence(
|
||||
$to['owner'],
|
||||
$fromWref,
|
||||
2,
|
||||
$troopsTime
|
||||
);
|
||||
|
||||
/*
|
||||
* Unit-urile din reinforcement sunt stocate in coloanele
|
||||
* corespunzatoare tribului proprietarului.
|
||||
*/
|
||||
$t1 = (int)($enforce['u' . $start] ?? 0);
|
||||
$t2 = (int)($enforce['u' . ($start + 1)] ?? 0);
|
||||
$t3 = (int)($enforce['u' . ($start + 2)] ?? 0);
|
||||
$t4 = (int)($enforce['u' . ($start + 3)] ?? 0);
|
||||
$t5 = (int)($enforce['u' . ($start + 4)] ?? 0);
|
||||
$t6 = (int)($enforce['u' . ($start + 5)] ?? 0);
|
||||
$t7 = (int)($enforce['u' . ($start + 6)] ?? 0);
|
||||
$t8 = (int)($enforce['u' . ($start + 7)] ?? 0);
|
||||
$t9 = (int)($enforce['u' . ($start + 8)] ?? 0);
|
||||
$t10 = (int)($enforce['u' . ($start + 9)] ?? 0);
|
||||
$hero = (int)($enforce['hero'] ?? 0);
|
||||
|
||||
/*
|
||||
* Nu cream movement pentru un reinforcement gol.
|
||||
*/
|
||||
if (
|
||||
$t1 <= 0 &&
|
||||
$t2 <= 0 &&
|
||||
$t3 <= 0 &&
|
||||
$t4 <= 0 &&
|
||||
$t5 <= 0 &&
|
||||
$t6 <= 0 &&
|
||||
$t7 <= 0 &&
|
||||
$t8 <= 0 &&
|
||||
$t9 <= 0 &&
|
||||
$t10 <= 0 &&
|
||||
$hero <= 0
|
||||
) {
|
||||
$database->deleteReinf((int)$enforce['id']);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Creeaza attack record pentru retur.
|
||||
*/
|
||||
$reference = $database->addAttack(
|
||||
$fromWref,
|
||||
$t1,
|
||||
$t2,
|
||||
$t3,
|
||||
$t4,
|
||||
$t5,
|
||||
$t6,
|
||||
$t7,
|
||||
$t8,
|
||||
$t9,
|
||||
$t10,
|
||||
$hero,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
/*
|
||||
* Creeaza EXACT UN singur movement de retur.
|
||||
*/
|
||||
$now = time();
|
||||
|
||||
$database->addMovement(
|
||||
4,
|
||||
$oasisWref,
|
||||
$fromWref,
|
||||
$reference,
|
||||
$now,
|
||||
$now + $time
|
||||
);
|
||||
|
||||
/*
|
||||
* Acum reinforcement-ul nu mai poate fi procesat din nou,
|
||||
* deoarece row-ul este sters IN TIMP CE lock-ul este detinut.
|
||||
*/
|
||||
$database->deleteReinf((int)$enforce['id']);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function sendTroops($post) {
|
||||
@@ -639,23 +957,38 @@ class Units {
|
||||
$to = $database->getVillage( $enforce['from'] );
|
||||
$Gtribe = ($ownerTribe = $database->getUserField( $to['owner'], 'tribe', 0)) == 1 ? "" : $ownerTribe - 1;
|
||||
|
||||
for ( $i = 1; $i < 10; $i ++ ) {
|
||||
if ( isset( $post[ 't' . $i ] ) ) {
|
||||
if ( $i != 10 ) {
|
||||
if ( $post[ 't' . $i ] > $enforce[ 'u' . $Gtribe . $i ] ) {
|
||||
$form->addError( "error", "You can't send back more units than you have" );
|
||||
break;
|
||||
}
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
if (!isset($post['t'.$i])) {
|
||||
$post['t'.$i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $post[ 't' . $i ] < 0 ) {
|
||||
$form->addError( "error", "You can't send back negative units." );
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$post[ 't' . $i . '' ] = '0';
|
||||
}
|
||||
}
|
||||
if (!is_numeric($post['t'.$i])) {
|
||||
$form->addError(
|
||||
"error",
|
||||
"Invalid troop amount."
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
$post['t'.$i] = (int)$post['t'.$i];
|
||||
|
||||
if ($post['t'.$i] < 0) {
|
||||
$form->addError(
|
||||
"error",
|
||||
"You can't send back negative units."
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($post['t'.$i] > (int)$enforce['u'.$Gtribe.$i]) {
|
||||
$form->addError(
|
||||
"error",
|
||||
"You can't send back more units than you have"
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( isset( $post['t11'] ) ) {
|
||||
if ( $post['t11'] > $enforce['hero'] ) {
|
||||
$form->addError( "error", "You can't send back more units than you have" );
|
||||
|
||||
@@ -36,11 +36,59 @@
|
||||
}
|
||||
$wwMultiplier = $wwFactor / 0.25; // 1.0 fara Waterworks, 1.5 la nivel maxim
|
||||
|
||||
if (isset($_GET['gid']) && $_GET['gid'] == 37 && isset($_GET['del']) && $database->getOasisField($_GET['del'], 'owner') == $session->uid) {
|
||||
$units->returnTroops($village->wid, 1);
|
||||
$database->removeOases($_GET['del']);
|
||||
header("Location: build.php?id=" . $id . "&land");
|
||||
exit;
|
||||
if (
|
||||
isset($_GET['gid']) &&
|
||||
(int)$_GET['gid'] === 37 &&
|
||||
isset($_GET['del'])
|
||||
) {
|
||||
$oasisWref = (int)$_GET['del'];
|
||||
|
||||
/*
|
||||
* Verificam server-side ca:
|
||||
*
|
||||
* 1. oaza exista;
|
||||
* 2. apartine jucatorului;
|
||||
* 3. este cucerita de SATUL CURENT.
|
||||
*
|
||||
* Nu este suficient doar owner == session uid deoarece un jucator
|
||||
* poate avea mai multe sate si mai multe oaze.
|
||||
*/
|
||||
$oasisOwner = (int)$database->getOasisField(
|
||||
$oasisWref,
|
||||
'owner'
|
||||
);
|
||||
|
||||
$oasisConquered = (int)$database->getOasisField(
|
||||
$oasisWref,
|
||||
'conqured'
|
||||
);
|
||||
|
||||
if (
|
||||
$oasisWref > 0 &&
|
||||
$oasisOwner === (int)$session->uid &&
|
||||
$oasisConquered === (int)$village->wid
|
||||
) {
|
||||
/*
|
||||
* FOARTE IMPORTANT:
|
||||
*
|
||||
* Returnam doar trupele din OAZA SELECTATA.
|
||||
*
|
||||
* returnTroops($village->wid, 1) NU trebuie folosit aici,
|
||||
* deoarece acela proceseaza toate oazele satului.
|
||||
*
|
||||
* Functia are lock per oasis si re-citeste DB dupa lock.
|
||||
*/
|
||||
$units->returnOasisTroops($oasisWref);
|
||||
|
||||
/*
|
||||
* Dupa ce reinforcement-urile au fost returnate,
|
||||
* oaza este eliberata.
|
||||
*/
|
||||
$database->removeOases($oasisWref);
|
||||
}
|
||||
|
||||
header("Location: build.php?id=" . $id . "&land");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Explicit lookup, instead of the original repetitive switch:
|
||||
|
||||
@@ -137,7 +137,7 @@ if ($displayarray['vac_mode'] == 1)
|
||||
|
||||
<?php
|
||||
if ($displayarray['access'] == BANNED) {
|
||||
echo "<tr><td colspan='2'><center><b>".BANNED."</b></center></td></tr>";
|
||||
echo "<tr><td colspan='2'><center><b>BANNED</b></center></td></tr>";
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
@@ -273,22 +273,42 @@ else $create = 0;
|
||||
|
||||
if(isset($_POST['a']) == 533374 && isset($_POST['id']) == 39) $units->Settlers($_POST);
|
||||
|
||||
/**
|
||||
* Anularea unei miscari din rally point (linkul "x" din 16_walking.tpl).
|
||||
*
|
||||
* EXPLOIT REPARAT (duplicare de trupe): varianta veche verifica intai
|
||||
* "SELECT Count(*) ... where proc = 0 and moveid = X", apoi facea UPDATE proc = 1
|
||||
* si abia apoi addMovement(4, ..., ref-ul miscarii anulate). Intre COUNT si UPDATE
|
||||
* exista o fereastra TOCTOU: doua request-uri trimise simultan treceau amandoua de
|
||||
* COUNT si inserau DOUA movement-uri de retur cu ACELASI ref. Cron-ul livreaza
|
||||
* trupele o data pentru fiecare rand de movement, deci un ciclu trimite-anuleaza
|
||||
* dubla trupele. Repetat de cateva ori: 30k -> 60k -> 120k -> 242k -> ... -> milioane.
|
||||
*
|
||||
* Acum revendicarea se face intr-un singur UPDATE conditionat, in
|
||||
* $database->claimMovementCancel(), care verifica atomic: proc = 0, expeditorul e
|
||||
* satul curent, sort_type IN (3, 5) si fereastra de 90 de secunde. Returul se
|
||||
* creeaza doar daca ACEST request a fost cel care a schimbat randul.
|
||||
*
|
||||
* Doua lucruri semnalate, nu schimbate tacit:
|
||||
* - $q2 ("SELECT id FROM send ORDER BY id DESC") si $lastid erau cod mort:
|
||||
* rezultatul nu era folosit nicaieri. Eliminat.
|
||||
* - $_GET['id'] intra brut in antetul Location. Trecut acum prin (int),
|
||||
* ca in restul fisierului.
|
||||
*/
|
||||
if(isset($_GET['mode']) && $_GET['mode'] == 'troops' && isset($_GET['cancel']) && $_GET['cancel'] == 1){
|
||||
$oldmovement = $database->getMovementById($_GET['moveid']);
|
||||
$now = time();
|
||||
if(($now - $oldmovement[0]['starttime']) < 90 && $oldmovement[0]['from'] == $village->wid){
|
||||
$qc = "SELECT Count(*) as Total FROM " . TB_PREFIX . "movement where proc = 0 and moveid = " . $database->escape((int)$_GET['moveid']);
|
||||
$resultc = mysqli_fetch_array(mysqli_query($database->dblink, $qc), MYSQLI_ASSOC);
|
||||
if($resultc['Total'] == 1){
|
||||
$q = "UPDATE " . TB_PREFIX . "movement set proc = 1 where proc = 0 and moveid = " . $database->escape((int)$_GET['moveid']);
|
||||
$database->query($q);
|
||||
$end = $now + ($now - $oldmovement[0]['starttime']);
|
||||
$q2 = "SELECT id FROM " . TB_PREFIX . "send ORDER BY id DESC";
|
||||
$lastid = mysqli_fetch_array(mysqli_query($database->dblink, $q2));
|
||||
$database->addMovement(4, $oldmovement[0]['to'], $oldmovement[0]['from'], $oldmovement[0]['ref'], $now, $end);
|
||||
$moveid = isset($_GET['moveid']) && is_numeric($_GET['moveid']) ? (int) $_GET['moveid'] : 0;
|
||||
|
||||
if($moveid > 0 && $database->claimMovementCancel($moveid, $village->wid)){
|
||||
$oldmovement = $database->getMovementById($moveid);
|
||||
|
||||
if(!empty($oldmovement)){
|
||||
$now = time();
|
||||
$end = $now + ($now - (int) $oldmovement[0]['starttime']);
|
||||
$database->addMovement(4, (int) $oldmovement[0]['to'], (int) $oldmovement[0]['from'], (int) $oldmovement[0]['ref'], $now, $end);
|
||||
}
|
||||
}
|
||||
header("Location: " . $_SERVER['PHP_SELF'] . "?id=" . $_GET['id']);
|
||||
|
||||
header("Location: " . $_SERVER['PHP_SELF'] . "?id=" . (isset($_GET['id']) ? (int) $_GET['id'] : 39));
|
||||
exit();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user