bully-re
Health Uyari
- License — License: GPL-3.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Gecti
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Reimplementation of the Bully: Scholarship Edition engine, read from the binary. Data loaders, collisions and Gamebryo NIF already verified against the real game files.
bully-re
English version. Version française.
A from-scratch reimplementation of the Bully: Scholarship Edition (PC, 2008)
engine, written by reading the original executable — in the spirit of
re3 and reVC for the GTA games.
Bully was built by Rockstar Vancouver on Rockstar North's GTA codebase, running
on Gamebryo 2.3 instead of RenderWare. That makes the GTA decompilations a
usable dictionary for roughly half the engine, and leaves the other half —
Gamebryo, and Bully's own action-tree AI — to be worked out from the binary.
The game is not included and never will be. This repository holds
hand-written code, analysis tools and documentation only. You need your own
copy of the game (Steam or disc) for anything here to run. Nothing derived from
the executable — no decompiler output, no extracted assets — is committed.
What is known so far
bully.exev1.0: 8 MB, unprotected, RTTI intact, Gamebryo 2.3, Lua 5.- 20,819 functions, 4.4 MB of code, 1,522 classes rebuilt from RTTI
(docs/classes.md), 66 inheritance roots. - The object model is GTA's:
CPlaceable → CEntity → CPhysical → CVehicle → CBike,CPed,CPlayerPed. Virtual method names and slot order from
re3/reVC are the starting point — and are verified, not assumed, because the
order does drift. - On top sits Bully's own action-tree system (
ActionNode,Condition*,*Track,*Objective) driving characters and activities. - The binary keeps 914 Lua API names in
{name, C function}tables. That
bridge names engine functions for free and is the single most productive
lever in the project (docs/api-lua.txt).
What is reimplemented, and verified byte for byte
Every data loader has a host test (tests/construire_tests.sh, thenBULLY_DATA=<game root> build/tests/test_…) that reads the real game files and
compares the result against values taken by hand from the originals.
| Subsystem | Coverage | Notes |
|---|---|---|
.img / .dir archives |
World.img 11,980 entries |
src/core/CdStream.h |
Binary model definitions .idb |
13 sections, all 77 files | docs/idb.md |
handling.cfg, *.dat |
cars, bikes, boats, gearbox, game-unit conversion | src/vehicles |
| Collision COL3 / COL2 / COLL | 488 files, 3,863 models, exact sizes | docs/collision.md |
Binary placements Ipl$ |
85 files, 11 section types | src/core/IplFile.h |
| Gamebryo NIF 20.3.0.9 | 5,724 files, 363,085 known blocks decoded | docs/nif.md |
Textures .nft |
4,469 files, all 35,635 textures decoded (DXT1, DXT5, RGB, RGBA, palette) | docs/nft.md |
| Streaming ids, pools | id ranges, 28 pools with entry sizes | docs/streaming.md, docs/pools.md |
| World grid and lists | 36x36 sectors, 32-bit list nodes, 10 entity pools | docs/world.md |
| NIF node transforms | rotation convention settled against 3,842 collision boxes | docs/nif.md |
Some findings along the way: 247 of the NIF files are big-endian, left over
from a console export; Bully ships Lua 5.0, not 5.1; and the .idb format
is the GTA IDE text format compiled to binary, with the section tags stored
backwards.
The biggest departure from GTA is the world's linked lists. Where re3 has a
three-pointer node, Bully packs a whole node into one 32-bit word: 4 bits
of entity pool, 14 bits of index in that pool, 14 bits for the next node's
index. A sector therefore stores handles, not pointers, and fits five lists in
20 bytes. See docs/world.md.
Building
No game code is needed to compile what is here:
./construire.sh # compiles every source file to an object
./tests/construire_tests.sh # builds the host tests
BULLY_DATA=/path/to/Bully build/tests/test_nif
construire.sh uses g++ only, no build system. Most tests need a real
installation of the game to read from; they never write to it. The seven tests
that read no game file (tests/sans_jeu.sh) run on every push and pull request
(.github/workflows/tests.yml), after compiling every source and every test.
Method
- Map first. RTTI, virtual tables, file formats, Lua bindings.
- Reimplement by subsystem, starting with what can be tested without a
renderer: data loaders,CPlaceable/CEntity, vehicles. - Verify each piece against the real files, with a test that fails loudly.
- Gamebryo is rebuilt as needed, only the parts the game actually calls.
Tools
- lip sync
.lipfiles, 493 files and 40,882 lines, 2 bits per frame read exactly asbully.exedoes (docs/lip.md); tools/ghidra/— headless scripts: inventory, RTTI export, per-vtable
decompilation, Lua binding extraction, string cross-references.tools/indexer.py+tools/chercher.py— build a local SQLite index of your
own analysis output and query it by offset, global, string, caller or
full text. The index is never committed.tools/generer_squelettes.py— generatesdocs/classes.mdandsrc/squelettes/from the RTTI dump.
Analysis runs in a container (blacktop/ghidra); the Ghidra project stays
outside the repository.
Status
5.8 % of bully.exe's code recreated (917 of 20,819 functions), 10.7 %
understood (code, docs, names). Breakdown in docs/avancement.md,
regenerated on every commit by tools/avancement.py.
Early. Everything that loads from disk is done and tested. The first visible
step is there: outils/nif2obj exports any model with its textures to OBJ,
following the game's own chain (model → texture dictionary → NIF node tree →
DXT decode), outils/rendu draws one with a dependency-free software
rasterizer, and outils/scene draws a whole zone from the game's own
placement files (docs/ipl.md). outils/visite walks through a zone in
real time, first person, still fully software-rendered at 400 × 240 (the New
3DS top screen) with perspective-correct texturing, near-plane clipping and
the game's own alpha modes; SDL2 only opens the window
(docs/rendu-temps-reel.md). Press F to walk instead of fly: the game's own
collision volumes carry you on the floor, up steps and against walls.
Outdoors, neighbouring zones load and unload as you move (outils/carte
maps the 85 placement files), so you can go from the school to the
business district. --pietons n places the game's pedestrians, deformed by
their own skeleton and animated with the game's own animations (the.agr animation format is worked out, docs/agr.md, in French):
they walk around, on the floor and against the walls.
The game's own Lua scripts run in outils/lancer_lua, on the original Lua 5.0.2
VM: main.lua and its threads frame by frame, the mission bank read from the
action trees, and the first mission, 1_01, played to MissionSucceed — its
cutscene 1-1-1 from Cuts.img (61.7 s), the area changes through the engine's
own state machine (fade, area scripts unloaded and loaded, doors paired byDATInit), the scripted speech, the pedestrians it creates and moves. Since 21.10,
on a new game, 1_02 follows on its own after 1_01 (mission node conditions
read: MissionCond, TimeCond, DayCond, NOT, OR), its narration is preloaded and
lasts as long as the real samples in Speech.bin, the player leaves the school
through a DT_ door, and the mission runs up to the training fight with Russell.
Since 22.10 the player's action controller descends into the tree that PedSetActionTree sets
(the 68 global action trees are loaded at start-up, as the engine does), so the training starts,
and with --jouer (block, then 0_BLOCK_0) the blocking lesson goes through. Since the second session of 22.10
the action nodes' conditions are judged (ConditionGroup, WeaponEquiped, ActionRequest, ActionNodePlayTime,TargetPlaying, LuaCond) and each pedestrian has an action controller that steps back into its tree when the
played node ends: the button-sequence lesson, the grapple and its charge go through (--demander, --sequence). Since the third session of 22.10 the passes come from the tracks themselves, as in the engine: Opportunity (and its random latch), Sequence and PlayOnTarget, with their priority rule; the pushed bully plays PushFwd/RCV and the grapple lesson ends. Since the fourth session of 22.10 hits land: Collision tracks (the hit reaches the locked target, as the tool has no bones), the target's reaction through the hit tree (ConditionFlag, MePlaying), a Sequence that leaves the set tree, and animation ends read from MAINPED.HXD; once pushed, the bully falls, and the heavy ground kick makes him play GroundHitHeavy: the ground-strike lesson goes through and 1_02 shows its fight objective. Since the session of 23.10 hits hurt: an Execute adds the target node's tracks to the controller's own list, as the engine does, and the DamageHandle of HitTree/…/Base/HitBase takes the received hit's damage; peds get their pedstats.dat entry (health, regeneration, humiliation threshold), the SetHealth, SetPedFlags and SetTargetAsGrappleTarget tracks are played and eleven hit conditions are judged (HitTime, Health, IsHealthAtHumiliationLevel…); in 1_02 the bully gets back up, eleven jabs bring him down to 5 health and the mission asks to humiliate him (1_02_HumiliateHim). Since the evening of 23.10 1_02 ends passed (119 s of play): the victim's humiliation group (+0xd44, picked by size once its health drops below half, through the ManageTargets track) is read by HumiliationGroupIs; an Opportunity's own conditions are judged (the file word reads 0, the second back-reference table points to a condition list: 951 of the 3,080 boot-time opportunities have one); animation groups (LoadAnimationGroup, AnimationGroupLoaded), held speech lines (SpeechTrack, SpeechStartTrack, IsSpeechPreloaded), TargetRelativePosition, FaceTarget and SeekEntityPosition are played, and PedFlags is judged. The bully plays the real 5-8_NoogieSpit humiliation, loses his last points through HumiliateFlee, plays HitTree/KnockOuts, and F_SetupRussellFight runs the Russell scene before MissionSucceed. Since the night of 23.10 the bully's AI tree is played instead of a scripted attack gesture: PedSetAITree sets a second controller per ped, PedAttack pushes a real AttackPedObjective onto the ped's objective queue, eighteen AI conditions are judged (Objective, AcquireTarget, JoinCombatZone, InCombatZone, Interval, WeightedRandom, ActionNodeRequest…; LuaCond now reads the script's result as a number, so 0.0 is false), and the ManageTargets, CombatLoco, ActionNodeRequest, spawn (with its own controller), ClearCurrentObjective and FleeObjective tracks are played; a node lives while one of its tracks with byte +8 is alive, as in the engine. In 1_02 the bully keeps to his 1.5 m combat zone, his CombatActions loops, the humiliation group comes from the ManageTargets update, the humiliation hands control back to the player's tree, and the fleeing bully falls without hitting the player: 1_02 still ends passed (119.03 s). Since 26.10 the action trees' mounted references are read: a referenced file (Locomotion.act, used by 42 files) exists once under /Global, and its parent depends on the context key, the name of the top-level tree (FUN_005f6960, FUN_005f6800, FUN_005f6290); LuaCond calls its function inside the mission's namespace (NSCall): during the block lesson the bully really attacks the player (Offense/Short/Strikes, through Locomotion.act's ActionOpps); ped type and sex, the motion intention, chapter and season, and sixteen more conditions are read. 1_02 still ends passed (119.03 s). Since 27.10 the attack phase held by the Collision track (CollisionStart, AttackPhase, AttackTimer), the HUD components, the camera manager, the controllers and the social state are read, with sixteen more conditions; CombatLoco's speed comes from its step (FUN_00625970): the bully reaches his zone at a sprint. 1_02 still ends passed (119.03 s). On the evening of 27.10 the AI's speed rules are read (inputs cleared by FUN_005f38e0 and the IDLE track, the navigation's speed FUN_005e4a60, CombatLoco's four sub-states, stopping at the small radius: the bully circles the player facing him), AI peds press buttons (ActionRequest reads the raw button), and the bully blocks: button 12 sends him into Defence/Block, and a hit taken there goes to HitTree/Standing/BlockHits; a taken Opportunity ends its node even without a target node. Eight more conditions are judged (WeaponNeedInit, ConversationActive, OnMission, MissionCond, PedIsInOutfit with the player's wardrobe, AttackerConditionGroup…). 1_02 ends passed at 121.94 s, with fifteen jabs. On the night of 27.10 a ped no longer dies at zero health but through the Death track, as in the engine; 1_02 has no unread condition left (MissionUnlocked, ParametricTime, PropTargetInteractive), ped vision (the cone and range from pedstats.dat) and grapple strikes (DamageTarget) are read, and 1_03 starts: PedFollowPath and the FollowPath track walk the player and Davis along their paths, up to the chase the tool does not play yet. On 02.11 1_03 gets as far as the garage: the tool plays the chase (--suivre, --viser), a received hit notifies the scripts (RegisterPedEventHandler), path-node actions, ped flags, stats and attitudes are read, and the AI picks its target as the engine does (FUN_004992a0); 1_02 still ends passed (121.94 s). On 04.11 1_03 ends passed (148.90 s): trigger events (RegisterTriggerEventHandler, tracked every frame for every ped), the action trees' Script track (the garage door being opened calls into the mission), the player's own trees (/Global/Player, /Global/PlayerAI), PlayerSetControl, tethers, ignored stimuli and door state are recreated; with the tool's counterattack the player beats the bullies, opens both garage doors, beats the waves and hits Davis on his barrels until CutsceneDavisBegs and MissionSucceed. On 05.11 1_04 ends passed (81.05 s): weapons (the ObjectButes weapon types, the inventory's shared pool, the weapon in hand taken through Weapons/SelectActions and its tracks), projectiles (WeaponFire, its ballistic launch speed, the marble and the brick), breakable objects from the .ipb spec entries with their hash events, prop action trees streamed from Stream/World.img, global event handlers and pickups are recreated; the player picks the slingshot, breaks three bus windows, climbs the tree and shoots the six jocks during their jumping jacks. 1_03 can now end with bricks thrown at Davis from afar (148.04 s), and 1_02 takes its real path, no longer a stub's (132.92 s). In the night of 05 to 06.11, 1_05 ("Save Algie") reaches its third stage: the prefects' patrols (the patrol path manager: the mission looped on AreaDisablePatrolPath), PedIsHit, the ally group (PedRecruitAlly, PedDismissAlly, eight conditions), the TetherLoco track that makes Algie follow, allies going through doors with the player, PedSetMissionCritical, PedFindInAreaXYZ and four pedestrian and speech functions are recreated; the player beats the first bully and the right-hand gang, enters the school with Algie, takes him to the second-floor toilet stall, beats the ambush, and Algie, hands washed, follows him to his locker. What comes next waits for the triggers' animated objects (the locker).
The tool plays the player (--entrer, --aller, --zone, --porte, --jouer, --demander, --sequence); everything
else answers like the engine does, or says it is a stub. See docs/lua.md, docs/missions.md,docs/zones.md, docs/cinematiques.md (in French).
See docs/journal.md for the running log (in French).
Next
See docs/suite.md (in French): the first open item is the next piece of work.
Legal
This project contains no code, assets or data from Bully: Scholarship Edition.
It is an independent reimplementation written for interoperability and study,
distributed under the GPL-3.0. Bully is a trademark of Take-Two Interactive;
this project is not affiliated with or endorsed by them.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi