How I used ChatGPT to mod Shi No Numa
This is how I used ChatGPT to get all eight Black Ops 1 perk machines into Shi No Numa. I thought the map was getting a little stale, and wanted to see how far I could take modding with it.
- Game build
- Black Ops 1 · Xbox 360 TU11
- Result
- Playtested in Xenia
- Hut selection
- 4 unique awards from 8 perks
- Physical Xbox
- Not yet validated
Why I wanted to try this
Shi No Numa was starting to feel a little stale to me. I wanted to change it, and I was curious whether I could get ChatGPT to do the modding. I have a lot of hardware for running local LLMs, so the original idea was to have ChatGPT get the tools and workflow working, then see if my local models could take over. The work described here was done with ChatGPT.
We started with GSC, the scripting language these games use for a lot of their gameplay logic. That meant changes like getting solo Quick Revive to work in World at War, or experimenting with the number of zombies that could spawn in Shi No Numa. Those were useful places to start, but I still wanted something more interesting to play.
So I asked for all eight Black Ops 1 perk machines in Shi No Numa. I wanted Stamin-Up, PhD Flopper, Deadshot Daiquiri and Mule Kick in the hut roulette alongside the original perks, with their proper models and artwork. I thought this would be a bigger version of the scripting work we had already done. It turned out to be much more complicated.
Why the Xbox version was harder
On PC, Black Ops has Mod Tools, and community projects such as LinkerMod extend them to build and load mods and custom maps. That gives PC modders an existing route for packaging content. Those tools do not produce an Xbox 360 plugin or make a PC asset package usable in our Xbox build.
We used CoD Xe, an existing Xbox 360 modding project, as the starting point for loading scripts and running custom code. The missing part was the asset import. A GSC script could ask for a machine by name, but Shi No Numa did not have all of that machine’s data loaded. The game could return a placeholder instead of the model we wanted.
A perk machine is more than its 3D shape. It also needs textures, materials that bind those textures to the model, and shader programs for things like the lit sign. The Xbox version stores that data in console-specific layouts, including texture tiling and byte order. We had to reverse engineer how its asset loader connected those pieces, then extend our CoD Xe plugin to recreate them in Shi No Numa.
5.5 set up the tools and test runner
ChatGPT 5.5 did a lot of the setup. We ended up with a Windows 7 SP1 virtual machine running Visual Studio 2010 SP1 and Xbox 360 SDK 21256.3. That was where the Xbox plugin could be compiled. On the Linux side, the Windows version of Xenia ran through Proton so we could test the game on the Ubuntu server.
The SDK supplied the Xbox compiler, linker and libraries needed to turn our C++ plugin into codxe.xex, an executable the Xbox environment could load. The installed documentation identifies it as the September 2013 XDK. Getting the tools installed was only part of it: the project was linking d3d9ltcg.lib, which triggered link-time code generation and ran the linker out of memory. Switching that dependency to d3d9.lib, along with the recorded project-setting changes, got the build working.
ChatGPT could send a build into that VM through WinRM, Windows’ remote management interface, collect the output and stage the resulting plugin for Xenia. I could still open Visual Studio to look at the project, but the repeated builds did not depend on someone clicking through the IDE. That distinction mattered once we were changing individual fields and testing one candidate after another.
The automated test runner was a big part of making this workable. ChatGPT could launch BO1, replay controller inputs through the menus, load a map and save screenshots and logs. It could then change the plugin, rebuild it and try again. Thankfully, I did not have to sit there doing the same menu navigation for every attempt.

There were a lot of attempts at getting assets out of the game’s FastFile archives and back into a different map. Some of that work was useful, but one approach that looked close to finished turned out to be built around the wrong interpretation of the archive structure. By the end of the 5.5 effort, I still had not seen the missing machines imported with their proper geometry and artwork. Mule Kick was an easier case because its assets were already available; it did not prove that the other imports worked.
Finding the Xbox asset loader
We did not have Treyarch’s C++ source. ChatGPT had to work from the Xbox executable, the code Xenia had actually loaded and the behavior of the game. The title update mattered here. The June 24 capture was the TU11-patched default.xex image, with its hash checked against Xenia’s loaded-image record. Addresses from a different build would have sent the investigation to the wrong instructions.
A helper wrapped the 5,373,952-byte code dump as elf32-powerpc and used LLVM to disassemble it at its real starting address, 0x82100000. The resulting text was about 97 MiB. ChatGPT wrote a script to decode direct PowerPC calls and rank their targets, then inspected the useful candidates. That pass found 91,301 direct calls and 481 calls with a nearby asset-type value in register r3, including 5 for models, 6 for materials and 8 for images.
Those were leads to test. The plugin later intercepted DB_FindXAssetHeader at 0x821F0050 and logged the vending-machine lookups. A nonzero pointer initially looked encouraging, but the game could create a fallback entry when an asset was missing. Comparing the lookup with the entry search showed why a machine name could resolve without producing a usable machine.
The import eventually needed its own copies of the whole dependency graph. A pointer into an Ascension allocation stops being useful when that donor map unloads. Our NMC1 cabinet package preserved the geometry and material mapping; the plugin allocated storage, rewired pointers and registered the imported assets in Shi No Numa. These are some of the layouts the working TU11 importer uses:
- XModel · 248 bytes
- The model root points to its surfaces, material handles, bone data and streaming bounds.
- XSurface · 136 bytes
- Each surface needs its vertex and index data rebuilt in memory the renderer can use.
- GfxImage · 156 bytes
- The original Xbox image record carries information that a PNG or a shortened header cannot preserve.
Even a one-byte offset mattered. In the live material header, the texture count was at byte 103, with the texture-table pointer at byte 116. Reading the next byte as the texture count produced the wrong interpretation. These values were established for this Xbox TU11 build; a PC structure definition was not a substitute.
There was another limit to the tooling: the generic LLVM decoder did not reliably name every Xbox VMX128 vector instruction. A long disassembly listing did not mean every instruction had been decoded correctly. The collision fix below relied on the scalar load, compare and branch sequence, backed up by the failing and passing runs in Xenia.
5.6 got the geometry working
When I moved the project to ChatGPT 5.6 Sol, I started seeing more progress on the reverse engineering. It recovered the test runner and kept trying changes in Xenia. Eventually the missing machine geometry appeared in Shi No Numa. Stamin-Up looked like a Stamin-Up cabinet, and PhD had its proper shape. That was the improvement I had been waiting to see.
The artwork was still a problem. We spent a lot of time on Deadshot Daiquiri, and it kept coming back blurry or with the wrong visuals. At one point 5.6 tried generating or restoring a higher-resolution version of the image. I did not want a replacement drawing. I wanted the actual artwork that was already in Black Ops 1.


Looking back through the technical notes, the early captures were sometimes only 32 × 32 or 128 × 128 pixels, even though the original machine loaded a more detailed texture. Some of the data visible to the CPU was stale while the GPU had a newer version. Upscaling the captured image could not recover detail that had never been captured in the first place.
Deadshot HUD icon fix
There were two different blur problems. The small perk icon on the HUD was fixed during the 5.6 work: it kept the original 32 × 32 image and removed unwanted mip allocations, which are lower-resolution copies. The world-space cabinet needed its real mip chain for different viewing distances. Fixing the HUD did not solve the cabinet texture.
6 Astra recovered the original artwork
The next big change came when I moved the project to ChatGPT 6 Astra. By then it had the working geometry, the runner and a lot of failed experiments to work from. From my side, the artwork finally came together in roughly a day of that push. It felt like the project moved forward much faster, although all that earlier setup was already there for it to use.
The important difference was how it captured the texture. In Shangri-La, where Deadshot already works, the body texture finishes loading at 512 × 512 pixels. The game also keeps smaller versions, called mip levels, for rendering at different distances. Astra enabled Xenia’s GPU memory readback during capture so the import tools could read the updated image from graphics memory. It also corrected a 4 KB offset in the memory reader that had pointed some checks at the wrong bytes.
It then preserved the original Xbox image layout and pixel data, copied the base texture and mip levels into their own memory, and adjusted the addresses the game used to find them. A temporary bright pink texture helped check that the renderer was actually reading the imported image. Once that worked, the original artwork went back in. The normal-play build did not need the capture-only readback setting left on.
The SDK function XGGetTextureLayout identified the base and mip storage, including the regions that actually contained image data. The importer copied those occupied regions and cleared unused padding. That is why comparing entire allocation hashes could be misleading: padding could differ while the relevant pixel bytes matched. Keeping the original tiling, byte order and GPU resource layout was part of the import, along with the visible artwork.
The lit signs needed more work too. Having the right shader name was not enough when Shi No Numa did not contain the shader programs behind it. Astra imported that rendering data as well. The recorded package contains 18 techniques and 51 shader functions. That let the cabinet render with its own lighting, and the same approach carried Stamin-Up and PhD over afterward.
Reading the PowerPC behind the hut crash
I still needed to play it normally. In one test I opened the first hut, saw Double Tap, and the game crashed before I bought anything. The automated tests had missed that. My report gave ChatGPT something specific to reproduce, and the trace led to a hidden Stamin-Up model that was still taking part in collision checks.
The imported cabinet had render geometry, but it did not include the donor’s complete rigid collision-tree graph. The game’s DObj trace still reached that hidden model and tried to use a null collision node. Calling Hide() and NotSolid() had not excluded it from that path. Moving the test camera farther away had also let earlier tests miss the failure.
The useful sequence was inside XModelTrace, whose entry point is 0x823A3F60. Here are the same instructions as selectable text. The decoder prints register numbers without an r prefix:
823a3fb4: a0 d7 00 ce lhz 6, 206(23)
823a3fb8: 2b 06 80 00 cmplwi 6, 6, 32768
823a3fbc: 41 98 00 14 bt 24, 0x823a3fd0
823a3fc0: 38 60 ff ff li 3, -1
823a3fc4: 38 21 02 10 addi 1, 1, 528
823a3fc8: cb e1 ff 60 lfd 31, -160(1)
823a3fcc: 48 24 69 94 b 0x825ea960lhz loads a 16-bit value and zero-extends it. Here it reads the field at r23 + 206. The next instruction compares it with 32768, or 0x8000, and records the result in condition-register field 6. Bit 24 is that field’s less-than result: if set, the branch continues at 0x823A3FD0. Otherwise this path sets the return value to -1 and goes through the function’s exit sequence.
That exposed an existing way to skip detailed model collision. The field is a signed 16-bit collision LOD, so storing -1 gives it the bits 0xFFFF. After the zero-extending load, that is above 0x8000 and takes the early-return path. The corresponding line in our C++ importer is:
reinterpret_cast<short *>(c->root + 206)[0] = -1;It applies only to the private display cabinets. The original hut clip still blocks the player, and stock machine models keep their collision. ChatGPT reproduced the crash on the previous build, then ran close approaches, shooting and purchases at all four huts on the corrected build. That gave us a regression test for the exact failure I had found.
Why ChatGPT was useful for this much trial and error
This is where ChatGPT was much better suited to the work than me carrying out every iteration manually. A single attempt could mean editing C++, sending an MSBuild job into Windows, collecting the XEX, checking its hash, starting Xenia through Proton, replaying the menus, waiting for the map and collecting the same screenshots and logs. Once those tools worked, ChatGPT could run that sequence, inspect the result and prepare the next attempt without needing me at every step.
It could also change the experiment itself. We could force a specific perk into the first hut, place the camera at a repeatable distance, wait for a texture to finish streaming or check whether two successive frames were identical because the game had frozen. Test scripts could check point deductions, purchases and returned controls. Each run saved its own evidence, so we could connect a failure to the plugin and test conditions that produced it.
That made a long sequence of small questions practical: was the lookup returning a fallback, was the texture still a placeholder, was the renderer reading our copy, did the old build fail the same test? ChatGPT wrote the scripts to reduce large dumps to specific leads and kept the build-and-test cycle moving as those questions changed. An experienced modder could build that automation too. For me, the advantage was having ChatGPT write, operate and revise it across the Windows and Linux tools while I concentrated on what I wanted to play and whether the result looked right.
My playtests still changed the direction of the work. I rejected artwork that its checks had accepted and found the hut crash that the earlier fixtures missed. Automating a weak check lets it repeat the same mistake quickly. The useful loop was taking those reports, making the failure reproducible and adding a test that would catch it the next time.
Playing it with all eight perks
After that, I could go back to playing it normally.
The version I accepted has all eight machines in the roulette, with four different perks chosen for the four huts each match. It keeps normal prices, the usual four-perk limit and 500 starting points. The cabinet artwork, drink bottles and HUD icons now use the original game assets. Perk jingles are still missing, and I have not finished testing it on a physical Xbox 360.

This started because I wanted Shi No Numa to feel less stale and wanted to see whether ChatGPT could set up modding for my local LLMs to continue. I ended up spending much more time on the asset imports than I expected. The useful part now is that the build tools, captures and repeatable tests are saved alongside the mod, so there is a concrete setup to continue experimenting with.
Test results and remaining work
Test results
- I accepted all four added perks after normal playtests in Xenia.
- Automated checks covered the roulette, hut purchases, bottles, HUD icons and returned controls.
- Fresh Shi No Numa and Ascension starts worked with the release active.
- A 42-second comparison across seven matched views found no material roulette slowdown in that sample.
Remaining tests and fixes
- Perk jingles.
- Loading and playing the mod on a physical Xbox 360.
- Long co-op sessions, high rounds and complete restart/revive coverage.
