mirror of
https://github.com/Shadowss/TravianZ.git
synced 2026-09-05 01:47:16 +00:00
Fix some atomic exploit
Fix some atomic exploit
This commit is contained in:
@@ -348,6 +348,130 @@ trait AutomationBattleResolution {
|
|||||||
return (mysqli_affected_rows($database->dblink) === 1);
|
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,
|
* Handle hero evasion: if the defender has evasion active and can afford it,
|
||||||
* send all defender units back to base and charge 2 gold + 1 evasion charge.
|
* send all defender units back to base and charge 2 gold + 1 evasion charge.
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ trait AutomationTroopMovements {
|
|||||||
$time = time();
|
$time = time();
|
||||||
$q = "
|
$q = "
|
||||||
SELECT
|
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
|
t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11
|
||||||
FROM
|
FROM
|
||||||
".TB_PREFIX."movement,
|
".TB_PREFIX."movement,
|
||||||
@@ -238,9 +238,11 @@ trait AutomationTroopMovements {
|
|||||||
$database->getOasisEnforce($vilIDs, 1);
|
$database->getOasisEnforce($vilIDs, 1);
|
||||||
|
|
||||||
foreach($dataarray as $data) {
|
foreach($dataarray as $data) {
|
||||||
if (!$this->claimMovementRecord($data['moveid'])) {
|
if (!$this->claimReturnMovementRecord(
|
||||||
continue;
|
$data['moveid'],
|
||||||
}
|
$data['ref'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$tribe = $database->getUserField($database->getVillageField($data['to'], "owner"), "tribe", 0);
|
$tribe = $database->getUserField($database->getVillageField($data['to'], "owner"), "tribe", 0);
|
||||||
$u = $tribe == 1 ? "" : $tribe - 1;
|
$u = $tribe == 1 ? "" : $tribe - 1;
|
||||||
|
|||||||
@@ -192,6 +192,52 @@ trait DatabaseMovementQueries {
|
|||||||
return (mysqli_affected_rows($this->dblink) === 1);
|
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
|
// no need to cache this method
|
||||||
function getA2b($ckey) {
|
function getA2b($ckey) {
|
||||||
list($ckey) = $this->escape_input($ckey);
|
list($ckey) = $this->escape_input($ckey);
|
||||||
|
|||||||
@@ -96,6 +96,79 @@ trait DatabaseTroopQueries {
|
|||||||
mysqli_query($this->dblink, "SELECT RELEASE_LOCK('enforce_$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
|
* Add the unit table(s) and troops if presents
|
||||||
*
|
*
|
||||||
|
|||||||
+365
-32
@@ -351,35 +351,353 @@ class Units {
|
|||||||
return "";
|
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) {
|
public function returnTroops($wref, $mode = 0) {
|
||||||
global $database;
|
global $database;
|
||||||
|
|
||||||
if(!$mode){
|
$wref = (int) $wref;
|
||||||
$getenforce = $database->getEnforceVillage($wref, 0);
|
|
||||||
foreach($getenforce as $enforce) $this->processReturnTroops($enforce);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check oasis
|
if ($wref <= 0) {
|
||||||
$getenforce1 = $database->getOasisEnforce($wref, 1);
|
return;
|
||||||
foreach($getenforce1 as $enforce) $this->processReturnTroops($enforce);
|
}
|
||||||
|
|
||||||
// set oasis to default
|
/*
|
||||||
if(count($getenforce1) > 0) $database->regenerateOasisUnits($getenforce1[0]['vref']);
|
* 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) {
|
private function processReturnTroops($enforce) {
|
||||||
global $database;
|
global $database;
|
||||||
|
|
||||||
$to = $database->getVillage($enforce['from']);
|
if (empty($enforce['id']) || empty($enforce['from'])) {
|
||||||
$tribe = $database->getUserField($to['owner'], 'tribe', 0);
|
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;
|
$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);
|
* Calculeaza timpul de mers Oaza -> Sat.
|
||||||
|
*/
|
||||||
|
$troopsTime = $this->getWalkingTroopsTime(
|
||||||
|
$fromWref,
|
||||||
|
$oasisWref,
|
||||||
|
$to['owner'],
|
||||||
|
$tribe,
|
||||||
|
$enforce,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
$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()));
|
* BUG FIX:
|
||||||
$database->deleteReinf($enforce['id']);
|
*
|
||||||
|
* 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) {
|
private function sendTroops($post) {
|
||||||
@@ -639,23 +957,38 @@ class Units {
|
|||||||
$to = $database->getVillage( $enforce['from'] );
|
$to = $database->getVillage( $enforce['from'] );
|
||||||
$Gtribe = ($ownerTribe = $database->getUserField( $to['owner'], 'tribe', 0)) == 1 ? "" : $ownerTribe - 1;
|
$Gtribe = ($ownerTribe = $database->getUserField( $to['owner'], 'tribe', 0)) == 1 ? "" : $ownerTribe - 1;
|
||||||
|
|
||||||
for ( $i = 1; $i < 10; $i ++ ) {
|
for ($i = 1; $i <= 10; $i++) {
|
||||||
if ( isset( $post[ 't' . $i ] ) ) {
|
if (!isset($post['t'.$i])) {
|
||||||
if ( $i != 10 ) {
|
$post['t'.$i] = 0;
|
||||||
if ( $post[ 't' . $i ] > $enforce[ 'u' . $Gtribe . $i ] ) {
|
continue;
|
||||||
$form->addError( "error", "You can't send back more units than you have" );
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $post[ 't' . $i ] < 0 ) {
|
if (!is_numeric($post['t'.$i])) {
|
||||||
$form->addError( "error", "You can't send back negative units." );
|
$form->addError(
|
||||||
break;
|
"error",
|
||||||
}
|
"Invalid troop amount."
|
||||||
}
|
);
|
||||||
} else {
|
break;
|
||||||
$post[ 't' . $i . '' ] = '0';
|
}
|
||||||
}
|
|
||||||
}
|
$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 ( isset( $post['t11'] ) ) {
|
||||||
if ( $post['t11'] > $enforce['hero'] ) {
|
if ( $post['t11'] > $enforce['hero'] ) {
|
||||||
$form->addError( "error", "You can't send back more units than you have" );
|
$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
|
$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) {
|
if (
|
||||||
$units->returnTroops($village->wid, 1);
|
isset($_GET['gid']) &&
|
||||||
$database->removeOases($_GET['del']);
|
(int)$_GET['gid'] === 37 &&
|
||||||
header("Location: build.php?id=" . $id . "&land");
|
isset($_GET['del'])
|
||||||
exit;
|
) {
|
||||||
|
$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:
|
// Explicit lookup, instead of the original repetitive switch:
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ if ($displayarray['vac_mode'] == 1)
|
|||||||
|
|
||||||
<?php
|
<?php
|
||||||
if ($displayarray['access'] == BANNED) {
|
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);
|
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){
|
if(isset($_GET['mode']) && $_GET['mode'] == 'troops' && isset($_GET['cancel']) && $_GET['cancel'] == 1){
|
||||||
$oldmovement = $database->getMovementById($_GET['moveid']);
|
$moveid = isset($_GET['moveid']) && is_numeric($_GET['moveid']) ? (int) $_GET['moveid'] : 0;
|
||||||
$now = time();
|
|
||||||
if(($now - $oldmovement[0]['starttime']) < 90 && $oldmovement[0]['from'] == $village->wid){
|
if($moveid > 0 && $database->claimMovementCancel($moveid, $village->wid)){
|
||||||
$qc = "SELECT Count(*) as Total FROM " . TB_PREFIX . "movement where proc = 0 and moveid = " . $database->escape((int)$_GET['moveid']);
|
$oldmovement = $database->getMovementById($moveid);
|
||||||
$resultc = mysqli_fetch_array(mysqli_query($database->dblink, $qc), MYSQLI_ASSOC);
|
|
||||||
if($resultc['Total'] == 1){
|
if(!empty($oldmovement)){
|
||||||
$q = "UPDATE " . TB_PREFIX . "movement set proc = 1 where proc = 0 and moveid = " . $database->escape((int)$_GET['moveid']);
|
$now = time();
|
||||||
$database->query($q);
|
$end = $now + ($now - (int) $oldmovement[0]['starttime']);
|
||||||
$end = $now + ($now - $oldmovement[0]['starttime']);
|
$database->addMovement(4, (int) $oldmovement[0]['to'], (int) $oldmovement[0]['from'], (int) $oldmovement[0]['ref'], $now, $end);
|
||||||
$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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header("Location: " . $_SERVER['PHP_SELF'] . "?id=" . $_GET['id']);
|
|
||||||
|
header("Location: " . $_SERVER['PHP_SELF'] . "?id=" . (isset($_GET['id']) ? (int) $_GET['id'] : 39));
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user