Quantcast

Jump to content

» «
Photo

San Andreas Save File Companion

91 replies to this topic
OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#1

Posted 21 July 2017 - 08:21 PM Edited by OrionSR, 06 September 2017 - 08:28 PM.

San Andreas Save File Companion

 

This topic is open to discussion of the details of all versions of San Andreas save files. It is intended to provide a wider context for the save structure than the wiki can provide, and a home for questions and investigations related to SA saves and documentation in the tradition of:

 

GTA III Save File Documentation and Vice City Save File Format

 

The official documentation of the save file structure for the PC version is available on the SA Save Wiki at GTAModding.com. This document serves as a baseline for all variations. The save wiki has been well tested for many years and is very accurate, but it could use a few updates, consistent formatting, and a modern naming convention for field variables.

 

SA Save Template for the 010 Editor: SAS.multi.bt (on GitHub)

 

010 templates can parse binary files into defined structures and variables. They are an invaluable tool for precisely documenting all known data and mapping out new structures. I'd prefer to use an open source tool but this is what we're working with now. Fortunately, the template files (*.bt) are in plain text and can serve as documentation for variations in saves of other systems.

 

SAS.bt - Original 010 template created by Seemann. PC version only. Includes checksum script.

SAS.multi.bt - Supports PC, PS2, PS2-J, and mobile versions of San Andreas.

 

I've been updating the multi template a lot recently.  It's a work in progress.

 

Links to Related Topics:

 

SA Save File Glitch Detection and Repair

San Andreas Enex Documentation

[SA] girlfriend globals / bitmasks

Blip/Marker Struct - memory and save info

Cheat Code Creation - PS2 save editing using AR-Max, Codebreaker, etc.

  • Seemann, Wesser, Nick007J and 2 others like this

Nick007J
  • Nick007J

    Mark Chump

  • Members
  • Joined: 17 Jan 2010
  • Russia

#2

Posted 21 July 2017 - 10:13 PM Edited by Nick007J, 21 July 2017 - 10:19 PM.

Mobile version additionally saves an index (2 bytes) of script allocated to the object in CTheScripts::ScriptsForBrains array. Rest of fields, as named by DK22Pac (unless I renamed them for my convenience):

+24 byte bBonusValue

+26 word wCostValue

+28 dword nRemovalTime

+32 dword EntityFlags

+36 byte const 0

+40 dword ObjectFlags

+44 byte bObjectType

+45 byte bColDamageEffect

+46 byte bStoredColDamageEffect

+47 byte PhysicalFlags [partial, custom]:

          1 DISABLE_COLLISION_FORCE

          2 FREEZE_POSITION

          4 IS_BULLETPROOF

          8 IS_FIREPROOF

          10 IS_COLLISIONPROOF

          20 IS_MELEEPROOF

          40 IS_EXPLOSIONPROOF

[+48 word wScriptTriggerIndex]

  • OrionSR likes this

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#3

Posted 22 July 2017 - 09:07 AM Edited by OrionSR, 22 July 2017 - 09:08 AM.

Thanks Nick. That will help a lot. Do you have the full reference? At first glance I'm having a difficult time aligning the hex and decimal offsets. 

 

DK22Pac... So this a reference for objects in PCv1 memory?

 

I'm anxious to start mapping things out against save records and to experiment with the flags but first I need to figure out why the timer reset fix is breaking Vice City saves. I've got reports that shopkeepers are refusing to spawn after the fix, and a glitch in a published tool has priority status.


Nick007J
  • Nick007J

    Mark Chump

  • Members
  • Joined: 17 Jan 2010
  • Russia

#4

Posted 22 July 2017 - 09:19 AM

DK22Pac... So this a reference for objects in PCv1 memory?


I used his field names for CObject structure in memory, as they are called in plugin-sdk.
https://github.com/D...me_sa/CObject.h

As I see it now, I only changed hungarian notation prefixes for consistency with how I use it. I haven't got to CObject myself yet so can't comment too much on their exact meaning, but I don't see any reason not to trust his names.

I'm anxious to start mapping things out against save records and to experiment with the flags but first I need to figure out why the timer reset fix is breaking Vice City saves. I've got reports that shopkeepers are refusing to spawn after the fix, and a glitch in a published tool has priority status.


I will comment in relevant topic soon, I already know why it happens as this is a crucial knowledge for speedrunning Vice City.

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#5

Posted 23 July 2017 - 01:40 PM Edited by OrionSR, 01 August 2017 - 09:50 AM.

0x38 - bObjectType?

I think I found a strategy to flag objects so they won't be saved. At save object record offset 0x38 is what I suspect is the Type field. The value is always 2 in my current set of examples; I suspect these are type 2 mission objects. When this value was edited to 6 to indicate  "OBJECT_MISSION2" the object is missing in the next save. In the PC version object type 0 will be removed, but on Android they are changed back to type 2 objects.

I managed a little progress on several of the flags. I'm not exactly sure if I've got the EntityFlags and ObjectFlags named properly. All offsets are for PC, and for these offsets anyway, appear to apply to Android as well. Unknown flags have been observed.
 

    0x2C Opcode	EntityFlags
      01  0382:	collision detection
      04  	unknown
      20  0392:	moveable
      80  0750:	visible
    0100	unknown
    1000	unknown
    4000  0418:	draw last
    8000	unknown
  010000	unknown
  020000  0905:	openable
  200000	unknown
08000000	unknown
10000000	unknown
    0x34 Opcode	ObjectFlags
      10	unknown
      20	unknown
      40	unknown
    0200  035D:	targetable
    0400  0723:	broken
    2000  08E9:	liftable (stealable)
    4000	unknown
    8000	unknown
  010000	unknown
  040000  0916:	attractive_to_magnet
  080000	unknown
0x3B	PhysicalFlags	
  01	Disable_Collision_Force (0550: keep object in memory)
  02	Freeze_Position
  04	Is_Bulletproof (09CA:)
  08	Is_Fireproof (09CA:)
  10	Is_Collisionproof (09CA:)
  20	Is_Meleeproof (09CA:)
  40	Is_Explosionproof (09CA:)

Based on the reference posted by Nick, I suspect but haven't tested:

0x39 bColDamageEffect
0x3A bStoredColDamageEffect

Opcodes that produced no changes in the save record:

0566: link_object $2658 to_interior 10
0654: make_object $2658 fireproof 1 (render scorched)
071F: set_object $2658 health_to 40
07F7: set_object $2658 destructible 1 // or 0
0875: set_object $2658 immune_to_nonplayer 1 // or 0
08D2: object $2658 scale_model 2.5
0906: set_object $2658 mass_to 1000.0 // float
0908: set_object $2658 turn_mass_to 500 // float (whoops)
04D9: set_object_records_collisions $2658 to 1

Specific Objects Blocking Missions

 

Burning Desire, Model ID 3029, cr1_door, $4052, $2665 PC, remove if handle does not match global

Architectural Espionage, Model ID 1491, Gen_doorINT01, remove all

 

Breaking the Bank at Caligula's...

  • 3037, warehouse_door2b, remove all
  • 3089, ab_casdorLok, $4090. $2703 PC, remove if handle doesn't match global
  • 2634, ab_vaultDoor, remove all
  • dkluin likes this

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#6

Posted 30 July 2017 - 06:22 AM Edited by OrionSR, 31 July 2017 - 11:16 PM.

Progress on the object structure has stalled, but I made enough progress on my templates to get a script working for the 010 editor that can remove the glitchy objects that block missions. I'm working to expand my scripts to remove most of the trash objects from mobile saves without removing the standard objects from the map. To do that, I needed a list of saved objects in an In the Beginning save and information on what's added or removed later by missions.

 

Saved Object List for In the Beginning

Spoiler

 

Saved Objects Changed by Missions  

Spoiler

 

Notes: 

 
Known to delete Export Blackboard if repeated after it's creation - very rare.
0108: destroy_object $201($200,46i) // Back to  School
0108: destroy_object $201($200,46i) // Bike School

It looks like the doors added by Small Town Bank were intended to be added during the initial game setup along with most of the other objects but they weren't removed from the mission cleanup list. These handles might reference objects that no longer exist early in the game.

 

Object groups like the bridge barriers are not controlled by this process.

Replacement objects for YKBB are controlled by another process.

 

Missions that use saved objects often destroy and replace the object during the mission. The replacement object will probably have a new handle which will change the index associated with each object within the save.


OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#7

Posted 01 August 2017 - 03:17 AM Edited by OrionSR, 01 August 2017 - 03:58 AM.

Notes on modifying San Andreas 010 templates to parse mobile saves:

 

I'm gathering the info I need for a multi-version SA template.

 

Block 0 - Misc: All messed up. I'll come back to this.

 

Block 1 - Script: 

 

Extra 4 bytes (int?) at +8.

    short   index;
    /* Script Thread */
    int     pNext <format=hex>, pPrev <format=hex>;
    int     mobileStuff0; //mobile

Mobile threads have 40 local variables.

    int     locals[40]; //mobile

There is extra information at the end of this block. I'm not sure what it does but I think I've managed to map it's structure well enough to handle most 1.06+ saves with the following changes to the end of block1's structure.

    int                 mobileStuff1; //mobile
    tThread             threads[nRunningThreads];
    WORD                unknownMobileCount; 
    WORD                unknownMobileA;
    WORD                unknownMobileB;

    struct tUnknownMobileStruct
        {
            int                unknownMobile;
            WORD               unknownMobile1;
            WORD               unknownMobile2;
            WORD               unknownMobile3;
        } unknownMobileStruct[unknownMobileCount];
} block1;

Block 2 - Player and Objects

 

Modified tPlayer struct

Spoiler

 

Modified Object Struct 

Spoiler

 

Block 9 - Markers

 

They added more markers in the 1.06 patch.

// Block 9: Markers
struct tBlock09_Markers
{
    tblockHeader    header;    
    tMarker         markers[250]; // mobile
} block9;

Block 23 - Gangwars

 

An extra byte at end.

    byte            unknown4mobile; // mobile
} block23;

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#8

Posted 07 August 2017 - 09:20 AM Edited by OrionSR, 09 August 2017 - 07:21 AM.

I'm trying to identify and document the fields of a "player car" structure unique to SA Mobile quick saves and auto-saves.  I'm hoping that if I can fill in enough details that the structure's elements might start to look familiar to people more experienced with this type of data.

 

The player car structure is located at +580 within the player struct of the Player and Objects block.

The structure length is 176 bytes for vehicles without passenger seats.

Vehicles that can support passengers have an additional 24 byte structure, which throws off the offsets at the end.

 

+0 dword vehicleHandle, unconfirmed

+4 dword modelID

+8 dword size of following data without optional structures

+12 72 bytes struct CMatrix

+84 byte radioStation (enum?)

+85 4 bytes colors

+89 (align)

+90 word alarmState

+92 byte nPassengers seats available 

+96 float wheelState

+100 float gasPedalState
+104 float breakPedalState
+108 8 bytes vehicleFlags

+116 float health

+120 int doorLock

+124 4 bytes physicalFlags

+126 byte immunity flags

+128 float mass

+132 float turnmass

+136 float buoyancy 

+140 24 bytes optional damageManager struct 

+140 2 bytes struct trains

+140 driverHandle

+144 8 dwords passengerHandles

 

The offsets above have been updated with available information.

The screenshots below were created with early guesses.

 

BMX and Camper:  SGMIQdcm.jpg

 

Expanded Rotations  JTfO6Zqs.jpg

 

Properties not saved in the player car structure are: mod parts, nitro, hydraulics, paintjobs, extra part variations, bombs, and custom plates. I don't expect to find handling flags if hydraulics don't save. 


Nick007J
  • Nick007J

    Mark Chump

  • Members
  • Joined: 17 Jan 2010
  • Russia

#9

Posted 07 August 2017 - 10:49 AM

Additions to the structure I managed to find.
+12 CMatrix (geometrical description of vehicle position in the world, three vectors and position (all with dummy 4 bytes after three floats), and 2 more zeros after it (unused in saved matrix), structure is 72 bytes)
+90 (2 bytes) alarm state
+96 (float) wheel state
+100 (float) gas pedal state
+104 (float) break pedal state
+108 (8 bytes) vehicle flags
+120 (int) [most likely 'projectile fire time' or 'door lock', I would say the latter, can't give exact answer because of minor vehicle structure shift in mobile version]
+124 (4 bytes) physical flags
+136 (float) buoyancy?
[optional fields]
+140 (for automobiles only) (CDamageManager) damaged state, 24 bytes
+140 (for trains only) unknown 2 bytes value; if train is a mission train, followed by value of CTrain::bDisableRandomTrains (1 byte)
[end of optional fields]
+140 (+offset) (int) driver handle
+144 (+offset) (int[8]) passengers handles
  • OrionSR likes this

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#10

Posted 08 August 2017 - 04:12 AM Edited by OrionSR, 08 August 2017 - 07:29 AM.

Trains? I doubt I would have thought of that. Thanks. But that means my template is still broken. I'm not sure if these are the same question but...

 

Have you seen any sign of an IS_TRAIN flag? Are the vehicle flags (and physical flags) well documented in other structures? 

 

Alarm State: a word? or state 0 and state 1?

 

Um... okay. It'll take a while to fill in the fields. I'll hold anymore questions until then.

 

No, wait. Damage manager? Okay, that makes sense but automobiles only? I'll recheck on a motorcycle but I thought I checked this on an NRG. My thought was the bicycles don't have this structure, which makes sense since bicycles don't take damage unless they flip perfectly and explode. I'm not sure how to flag this structure if nPassengers doesn't hold true. I think I'll take another look at your vehicle flags and see if I can align anything with the modelFlags in handling.cfg.

 

Edit 1) Dummy data in CMatrix: There is definitely data in these floats in a couple of these 100% SnP saves - BMX and NRG-500. It looks like the player was getting 100% from the Chiliad Challenge and the game saved while the bike was in motion. However, the vehicle doesn't seem to be moving during the fade-in from loading.

 

Edit 2) +120 confirmed as door lock. I locked the door with my editor and tweaked the ped handle so CJ would spawn on top of the camper. It should have been an easy test but the ped handle I chose put a driver in the car and she drove off when CJ jumped off the roof. So... driverHandle at +140(+offset) has been confirmed as well. CJ is driving if driverHandle == .player.handle, or -1.

 

+136 float values for:

  • Coach. 84.44444
  • Greenwood, 18.28572
  • Brown Streak, 48.88889
  • Camper, 17.88235
  • Tampa, 19.42857
  • MtBike & BMX, 0.776699

+89 byte, align? (unused bytes that pad memory so the next word. dword or structure can start on a doubly-even offset)


Nick007J
  • Nick007J

    Mark Chump

  • Members
  • Joined: 17 Jan 2010
  • Russia

#11

Posted 08 August 2017 - 09:10 AM

Dummy data in CMatrix: There is definitely data in these floats in a couple of these 100% SnP saves - BMX and NRG-500. It looks like the player was getting 100% from the Chiliad Challenge and the game saved while the bike was in motion. However, the vehicle doesn't seem to be moving during the fade-in from loading.


Oh right, you are correct, first 4 bytes (between first two vectors, so at +24) represent flags. I don't know their meaning, however.
 

Trains? I doubt I would have thought of that. Thanks. But that means my template is still broken. I'm not sure if these are the same question but...
 
Have you seen any sign of an IS_TRAIN flag? Are the vehicle flags (and physical flags) well documented in other structures?


Game uses "type" column from vehicles.ide to identify vehicle as a train. Besides general model info data, which holds this information, each vehicle contains a vehicle type in its memory structure.

Vehicle flags are described here:
https://github.com/D...e_sa/CVehicle.h
(starting with 'unsigned char bIsLawEnforcer : 1; // Is this guy chasing the player at the moment')

Physical flags are described here:
https://github.com/D..._sa/CPhysical.h
(starting with 'unsigned int b01 : 1;')

I have minor additions to it:
b01 => bIsHeavy
bInvulnerable => bIsOnlyDamagedByPlayer

I also have notes about b17 that it is calculated during collision testing and that it probably affects how collision should be processed this frame.
 

Alarm State: a word? or state 0 and state 1?


A word. -1 for "car has alarm system", values >=0 mean 'how many milliseconds remaining for alarm to be active' (when alarm is triggered this value is set to 15000).
 

No, wait. Damage manager? Okay, that makes sense but automobiles only? I'll recheck on a motorcycle but I thought I checked this on an NRG. My thought was the bicycles don't have this structure, which makes sense since bicycles don't take damage unless they flip perfectly and explode. I'm not sure how to flag this structure if nPassengers doesn't hold true. I think I'll take another look at your vehicle flags and see if I can align anything with the modelFlags in handling.cfg.


To be more certain, automobiles and everything derived from them (helis, monster trucks, planes, quadbikes and trailers). Bikes should not have this structure. This structure is described here:
https://github.com/D...DamageManager.h
 

+89 byte, align? (unused bytes that pad memory so the next word. dword or structure can start on a doubly-even offset)


Alignment, yes.
 

+136 float values for:

  • Coach. 84.44444
  • Greenwood, 18.28572
  • Brown Streak, 48.88889
  • Camper, 17.88235
  • Tampa, 19.42857
  • MtBike & BMX, 0.776699

Buoyancy is calculated as
B = m * 0.8 / ps
where m is mass of vehicle and ps is 'percent submerged' in handling data. Values you provide seem to correspond to this formula.
  • OrionSR likes this

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#12

Posted 08 August 2017 - 03:25 PM Edited by OrionSR, 09 August 2017 - 04:09 AM.

Thanks again. I won't be able to investigate this until after work but, when I saved a Brown Streak I had it running in full speed reverse but that dummy data was still blank. So, maybe something specific to a MtBike. Brake pedal state was active though.
 
Updated:______________________________________________________________________
 
As expected, an NRG broke my script. I'll get to work 
 
If I understand correctly, there's nothing in the save preceding the option fields to indicate vehicle type other than model IDs. So if I want to find the type properly then I need to parse vehicle.ide for use by my template. I may consider that later but for now I think I'll consider the automobile structure as default and run options for motorcycles and bicycles), and train models. I guess I'll learn more as I dig through the file. 
 
trains:
590 freibox
569 freiflat
537 freight
538 streak
570 streakc
449 tram
bikes:
581	bf400
523	copbike
462	faggio
521	fcr900
463	freeway
522	nrg500
461	pcj600
448	pizzaboy
468	sanchez
586	wayfarer
bmx: 
481	bmx
509	bike
510	mtbike
boat:
472 coastg
473 dinghy
493 jetmax
595 launch
484 marquis
430 predator
453 reefer
452 speeder
446 squalo
454 tropic
 
 
Damn. It'd be so much easier if it just saved these flags instead of that other useless junk.

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#13

Posted 09 August 2017 - 08:31 AM Edited by OrionSR, 09 August 2017 - 08:33 AM.

Can anyone explain this ped/object/weapon structure that was tacked onto the end of the script block? I've got it mapped out, but I don't understand what I'm reading well enough to label much of anything.

 

h5Xwdqjs.jpg

 

What's not shown in the screenshots is that the models tend to be added in reverse. Mobile2[0] is 75 (max records) if there is only one record in the structure, and 74 if there are two. MobileA = Mobile2[0] - 1.

    if (isMobile == 1) {
        WORD                MobileCount;    // nRecords
        WORD                MobileA;        // minIndex
        WORD                MobileB;        // 
        struct tMobileStruct
        {
            struct tModel
            {
                int                modelID;           // modelID
                WORD               Mobile1;          // type?
                WORD               Mobile2;          // index? [string length]
                WORD               Mobile3;          // maxIndex?
                if (modelID >= 290 && 299 >= modelID) {
                    char modelName[Mobile2];
                    int MobileInt <format=hex>;              //handle?
                }
            } Model[MobileCount] <optimize=false>;
        } MobileStruct;
    }

So... objects and weapons and ped (oh my). I've got it mapped and don't know what it does. I've read reports of extra Denise's hanging around after save exploits but I haven't looked into it yet. Any suggestions? 


OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#14

Posted 09 August 2017 - 09:25 AM Edited by OrionSR, 09 August 2017 - 09:28 AM.

Description of struct CMatrix (72 bytes):

 

I'm having a difficult time sorting out how I want to rework and describe the CMatrix structure. My thought is to organize most of the data into 4 structs of 4 floats, currently described as X, Y, Z and dummy. Description for structs would be:

 

+0 struct LevelToGround

+16 struct LookingDirection

+32 struct dymamic flight data?

+48 struct position

+64 dword unknownA

+68 dword unknownB

+72 (end)

 

I'm not at all confident in any of the descriptions besides position and (end).


Nick007J
  • Nick007J

    Mark Chump

  • Members
  • Joined: 17 Jan 2010
  • Russia

#15

Posted 09 August 2017 - 09:48 AM Edited by Nick007J, 09 August 2017 - 09:50 AM.

This mobile structure is generated by CScriptResourceManager::Save().

I documented this structure (for PC version) as follows:
 
struct CScriptResourceManager
{
  CScriptResourceEntry m_asResources[75];
};
struct CScriptResourceEntry
{
  int m_nResourceID;
  CRunningScript *m_pScript;
  short m_wType;
  char gap_10[2];
};
This structure holds information about which resources were requested. Following opcodes register resource in manager:

Type 1 (animation - values starting with 25575)
REQUEST_ANIMATION [+]
REMOVE_ANIMATION [-]

Type 2 (model - model ids; special chars have models 290-299)
LOAD_SPECIAL_CHARACTER [+]
REQUEST_MODEL [+]
MARK_MODEL_AS_NO_LONGER_NEEDED [-]
UNLOAD_SPECIAL_CHARACTER [-]

Type 3 (decision maker - likely to be high values)
COPY_SHARED_CHAR_DECISION_MAKER [+]
REMOVE_DECISION_MAKER [-]

(command IDs and opcode names correspond as here: http://pastebin.com/raw/kCwS6rdG)

If I understand correctly, this is how (probably) it should be stored in a save:
WORD count;
struct ResourceManager {
  WORD index; //index in manager array
  WORD scriptIndex; //index of a script
  int resourceID;
  WORD type;
  if (type == 2 && resourceID >= 290 && resourceID <= 299){
    int length;
    char modelName[length]
  }
} [count];
  • OrionSR likes this

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#16

Posted 09 August 2017 - 10:37 AM

Oh yeah. That's what I was watching when you were showing how the dealers spawn. 

 

Hm... To align with what I see, maybe...

WORD count;
struct ResourceManager {
  int resourceID;
  WORD type;
  WORD index; //index in manager array
  WORD scriptIndex; //index of a script
  if (type == 2 && resourceID >= 290 && resourceID <= 299){
    char modelName[index]
    int dunno;
  }
} [count];

Using index for length saves me the trouble of creating a local variable length = index. It doesn't make sense grammatically but that's how the data is used here.

 

 

Type 2 (model - model ids; special chars have models 290-299)

LOAD_SPECIAL_CHARACTER [+]
REQUEST_MODEL [+]
MARK_MODEL_AS_NO_LONGER_NEEDED [-]
UNLOAD_SPECIAL_CHARACTER [-]

How best to flag resources so they are removed when the save is loaded, or at least not saved again? I'm thinking 0 if that does everything with a [-] designation.

 

Do you have any suggestions for intentionally saving resources? Some saves may have as many as 5 or 6. I was amazed when I came across a save with the resources maxed out at 75. What would that do to a save?


Nick007J
  • Nick007J

    Mark Chump

  • Members
  • Joined: 17 Jan 2010
  • Russia

#17

Posted 09 August 2017 - 06:23 PM

How best to flag resources so they are removed when the save is loaded, or at least not saved again? I'm thinking 0 if that does everything with a [-] designation.
 
Do you have any suggestions for intentionally saving resources? Some saves may have as many as 5 or 6. I was amazed when I came across a save with the resources maxed out at 75. What would that do to a save?


Originally, resource manager is just informational, as far as I understand. It just registers that something was requested or marked as no longer needed. The only time this array is actually being checked is at the end of cutscenes, when presence of a model in the manager may block marking stuff as no longer needed. Presence of many resources may suggest that either mission might be badly programmed, or that one of missions was incorrectly finished (something related to duping, for example), or that a mission is currently running.

The manager in mobile version is more useful, most notably allowing saving during missions, so that models are loaded upon reload.

Is your first question about how to remove these resources from save? Probably setting type to 0 and resourceID to -1 should do the trick.

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#18

Posted 10 August 2017 - 01:51 AM Edited by OrionSR, 10 August 2017 - 02:09 AM.

Yes, I'm looking towards strategies to edit the saves; documentation is an important early step.  The primary focus is always on glitch detection and repair, but I always have an eye out for strategies to customize saved games. Can the resource manager be responsible for extra Denise's hanging around after dating exploits? Reports suggest that her body will persist if killed. I'm starting to doubt that these issues are related.

 

Removal strategy: Type 0 to force the game to remove the resource, that makes sense. Disabled modelID resourceID, to prevent changes to active resources when the save first loads? Disabling the resourceID will break my template if I run it on this save as the special actors will still have strings. 

 

To answer the question I was going to ask: resourceID is a super-set of model ID that also includes animation IDs and decision IDs.

 

Pending an extra Denise save to investigate, the next major save structure is liable to be difficult. Those slot 10 mission saves are larger by 65000 bytes; enough to hold the largest mission. I'm not sure what to expect. I haven't even adjusted my PC template for this purpose. Players can save missions on PC using a basic saveanywhere script that doesn't check for OM0. The saves just won't load unless you put the mission thread to sleep for a while, and that's harder to do with a broken template. (I could use an strategy to kill threads and missions using an editor. I always use cleo for this purpose.)

 

Here's a link to that save with 75 extra player resources. It's a 100% save named Wrong Side of the Tracks, so I would suspect some exploits were being used. Or a mission select mod. 

 

I've been able to save one player vehicle but not 2 or 3 like in some of these saves.


OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#19

Posted 11 August 2017 - 12:39 AM Edited by OrionSR, 11 August 2017 - 03:07 AM.

My first efforts to adapt my template for the larger slot 10 mission saves have failed miserably. I'll document what I've learned so far as a lead-in to another investigation.

 

If a PC player saves during a mission using a saveanywhere script that doesn't check for OM0 (not that that matters much anymore) the save will crash when loaded, but the glitch can be fixed by putting the thread to sleep and killing it with cleo. These saves load just fine in my template without modification, but it doesn't look like the "mission" gets saved, it's just a "thread" with the properties of the mission. SASE also reads these threads without issue. 

 

I tried again to kill an active mission thread on PC without any luck. Setting the wake time to something large keeps it from crashing right away, but tweaking other variables isn;t helping.

 

isActive = 0 loads but cleo scripts don't work so I can't save.

isActive = -1 can be saved but the mission thread persists in the save.

Fiddling with the short index at the start of the record always produced a crash.

 

I can't think of anything else that might cause the thread to end by randomly fiddling with the settings. Instead I think I'll look for something in the standard scripts that can kill a sleeping thread for me if I rename it properly. Any suggestions?

 

Can anyone explain the "range" of the pointers at the start of the thread structures. I'm trying to estimate the range of possible pointer values for pPrev (+6) and currentIP (+22) of the main thread, which should always be located at a static offset in mobile saves. My hypothesis is that I can detect the difference between iOS and Android pointers. Android uses global and local memory addresses quite differently than PC, leading to huge values for most pointers.

 

pPrev (dword preceding "main"):

A2886CF0 - Android

006B59B4 - iOS

00A90590 - PCv1

 

I'm not sure what to expect from the Remastered version. I have no known examples to work with. And I only have known iOS saves from one source, which isn't helping. Anyone have a save to donate? Anything will do - start a new game, move away from the bike so the safehouse icons can appear, and pause to produce a slot 10 quicksave. Android saves are useful too. I assume most of what I see are Android saves but I can't tell for sure yet.

 

Missions in Mobile Saves

 

From what I can tell the whole mission is saved in-line with the other threads as I see plenty of strings that associate with this mission, and there's a chance that other threads can exist after a mission thread. The structure varies from a thread right away - the thread name is offset by -5 and not even the index looks right. So... stumped again for a while. I think I'll go back to my global variable translation table and look for a what to convert it for 010 so I can finally get my object cleanup scripts working.

 

Added:_________________________________________

 

Can anyone explain the function of the short index that begins each thread? It seems to be the only hint that a mission is impending in a mobile save. Scanning through thread indexes I find a lot of values in the 0x45 range, give or take a dozen or so, but I haven't worked out a meaning of the values. But when I encounter a mission as the next thread, this example reads as 0x8046. So I'm thinking that 80 byte is flagging it as a mission. The index short is followed by an unknown mobile dword, always -1 as far as I've seen. Then I get 03A4: name_thread 'CRASH1' 0050: gosub and I get lost as I'm not as good at decompiling as Sanny is. How am I supposed to know where the mission ends? I just want to block around it.


OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#20

Posted 11 August 2017 - 06:56 AM Edited by OrionSR, 11 August 2017 - 08:51 AM.

SA Mobile Mission Saves - got it working for a single example.
 
Mission saves are created by the checkpoint system of some missions, and can also be enabled during other missions by failing and trying again. Mission saves are typically found in slot 10, the slot that is loaded when resuming a game after a fresh start or retying a failed mission. They are easily overwritten by quick-saving while pausing, but can easily be renamed to another slot. Mission saves are 65000 bytes larger than other mobile saves.
 
It doesn't make sense to define saves by their slot or save/load strategy. Any save will work in any slot. Auto-saves are written to and read from slot 9, and quick/checkpoint/resume saves are assigned to slot 10. Manage the slots to best suit your needs.
 
Before making changes to accommodate a mission thread, I had to move my unknown mobileStuff0 int before the pointers. To get the normal threads fixed. Then, if the index is negative then reserve 73096 bytes for mission code. I suspect there's stuff besides just code in this data, like local variables, but for now it's good enough for testing.
 
Update: I found the locals hiding behind the couch. 69000 bytes for mission code as documented elsewhere, and 1024 dwords for local variables. They're all empty though, except for a nominal value in the timers.



struct tThread
{
    short   index;
    /* Script Thread */
    if (isMobile==1) int mobileStuff0 <format=hex>; //mobile
    if (index < 0) missionThread = 1;
    if (index < 0) byte   missionCode[69000];
    if (index < 0) DWORD  localvar[1024];   
    int     pNext <format=hex>, pPrev <format=hex>;
    char    name[8];
    int     baseIP <format=hex>;
    int     currentIP <format=hex>;

If there's a missionThread then an 8 byte character field for the mission name is created at the end of all structures, follow by an unknown dword, and then the start of the next BLOCK begings.

 

Update: The character field for the mission name isn't holding true for other missions. 8 bytes are still required to round out the structure for mission saves but I'm not sure what these bytes are for.

        if (missionThread == 1) {
            char missionName[8];
            int uMobile1;
        }
    }
} Script;[size=4]
[/size]

Nick007J
  • Nick007J

    Mark Chump

  • Members
  • Joined: 17 Jan 2010
  • Russia

#21

Posted 11 August 2017 - 10:29 AM Edited by Nick007J, 11 August 2017 - 10:33 AM.

int mobileStuff0 is an index of a streamed script, if saved script is streamed. I am not sure if they can actually be saved (they certainly are not on PC).

There is a variable before block with mission name and "uMobile1", which is a value of "IsOddJob", which is set to 1 if a command CHECKPOINT_SAVE_ODDJOB (0A77) [with an exception of "crane2" in combination with OM1] has been executed before mission cleanup; or by command SCRIPT_NAME (03A4) with parameter "copcar", "ambulan", "firetruck" or "stunt". It being set to 0 + save having a mission script causes these two fields to be added.

uMobile1 is a value of a variable "missionReplaySetting". It is set to:
- 0 by CHECKPOINT_SAVE_ODDJOB (0A77) [same exception applies as above],
- 0 by LOAD_AND_LAUNCH_MISSION_INTERNAL (0417)
- 1 by CHECKPOINT_SAVE (0A6F) [unless first parameter is 99],
- 1 by SCRIPT_NAME (03A4) if current script is mission script, not a streamed script, currently on OM1 and IsOddJob == 0.

'missionName' is actually a name of mission text (LOAD_MISSION_TEXT, 054C), not the script itself (e.g. mission name of Management Issues is 'MUSIC3' and of mission text 'STRAP3').

As you correctly determined, game intentionally sets a bit in an index to mark script as a mission script (game checks by logical and with 0x8000 but checking sign does the same).
  • OrionSR likes this

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#22

Posted 11 August 2017 - 07:00 PM Edited by OrionSR, 11 August 2017 - 07:00 PM.

Ah, and there's that other structure we renamed in the topic but it never propagated to the script.

 

Search and Replace:

uMobile1 with IsOddJob

missionName with missionText

local missionThread with isMissionThread 

 

What is the proper form of the logical and with 0x8000? I haven't performed that operation yet.

        if (isMissionThread == 1) {
            char missionText[8];
            int isOddJob;
        }

The isMissionThread evaluation occurs at a lower level of the template so I can't query the index directly. I prefer to limit my locals if I can. As it turned out, I don't need to track isSlot10 (large mobile save).  isMissionThread should be true if any of the threads were mission threads.

 

I'll review the logic in your description but the primary question is, Is if (isMissionThread == 1) a complete evaluation? Are the Mission Code + Local Array and Mission Text + OddJob structures always paired or can I get some parts without the others?


Nick007J
  • Nick007J

    Mark Chump

  • Members
  • Joined: 17 Jan 2010
  • Russia

#23

Posted 11 August 2017 - 07:07 PM Edited by Nick007J, 11 August 2017 - 07:09 PM.

You probably misunderstood me (or I misunderstood the code). Following mission text there is "missionReplaySetting". "IsOddJob" is a variable (4 bytes) that preceeds this block. So, if I understand it right, it should be
 
int IsOddJob;
if (isMissionThread == 1 && IsOddJob == 0){
  char missionText[8];
  int missionReplaySetting;
}

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#24

Posted 11 August 2017 - 07:28 PM Edited by OrionSR, 11 August 2017 - 07:31 PM.

Ah. I think I found it.

    if (isMobile == 1)  int mobileStuff1;    //mobile
    tThread             threads[nRunningThreads] <optimize=false>;

So mobileStuff1 with isOddJob and missionReplaySettings fixed. How to test it?

 

And then I'm into the resource manager that never got updated in my template. I've got a lot of cleanup work to do but overall structure is the priority.

 

Thanks again for the support. I doubt I would have found the odd job stuff by experimentation.


Nick007J
  • Nick007J

    Mark Chump

  • Members
  • Joined: 17 Jan 2010
  • Russia

#25

Posted 11 August 2017 - 07:32 PM Edited by Nick007J, 11 August 2017 - 07:40 PM.

Hm, I would expect this variable to be right after resource manager and certainly after scripts. The variable before threads array is called SaveGameStateType.

Possible values are:
0 - done by CHECKPOINT_SAVE (0A6F)
1 - done by AUTO_SAVE (0A72)
2 - done by TERMINATE_THIS_SCRIPT (004E)
3 - done by OS_ApplicationEvent (possible when 'close app' event is triggered?)
4 - done by CHECKPOINT_SAVE_ODDJOB (0A77) and function CPad::CheckPointSave() [hard to say what latter one is, but it seems to be triggered when player is in a vehicle, and there are checks for model ids 537, 538 and 570)
5 - done by SCRIPT_NAME (03A4)

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#26

Posted 11 August 2017 - 07:46 PM Edited by OrionSR, 11 August 2017 - 08:34 PM.

Okay, I was hoping to  clean up the resource manager struct first but for now, here's a wider view on the structure.

    int                 nRunningThreads;
    if (isMobile == 1)  int SaveGameStateType;    //mobile
    tThread             threads[nRunningThreads] <optimize=false>;
    if (isMobile == 1) {
        WORD                MobileCount;    // nRecords
        WORD                MobileA;        // minIndex
        WORD                MobileB;        // maxIndex <= unexplained data
        struct tMobileStruct
            (snipped resource manager)
        if (isMissionThread == 1) {
//        if (isMissionThread == 1 && isOddJob == 0) {
            char missionText[8];
            int missionReplaySetting;
        }
    }
} Script; // end of block

It looks like the missionText string is often full of garbage data. MobileB looks like the most likely candidate. MobileA was usually consistent with the next available record index. 

 

It looks like I was never able to align your predictions for the resource manager structure with what I saw in the saves.


Nick007J
  • Nick007J

    Mark Chump

  • Members
  • Joined: 17 Jan 2010
  • Russia

#27

Posted 11 August 2017 - 08:22 PM Edited by Nick007J, 11 August 2017 - 08:43 PM.

Based on function CTheScripts::Save() in mobile version, this is what I would expect from a mobile save in this block.
 
int sizeOfVars;
char scriptSpace[*]; //* this block seems to be in chunks of 51200 bytes in PC version, and in chunks of 65000 bytes in mobile version, e.g. formally if sizeOfVars is 63000, the size should be 102400 on PC, and 65000 in mobile
tScriptForBrains scriptsForBrains[70]; (sizeof(tScriptForBrains) == 20)
int CTheScripts::OnAMissionFlag;
int CTheScripts::LastMissionPassedTime;
t2 CTheScripts::BuildingSwapArray[25] (t2: int type (2 or 0), int handle, int newModel, int oldModel)
t3 CTheScripts::InvisibilitySettingArray[20]; (t3: int type, int handle)
int CTheScripts::VehicleModelsBlockedByScript[20];
t4 CTheScripts::ScriptConnectLodsObjects[10] (t4: int objectId, int lodId)
t5 CTheScripts::ScriptAttachedAnimGroups[8] (t5: int model, char name[16])
char CTheScripts::bUsingAMultiScriptFile;
char CTheScripts::bPlayerHasMetDebbieHarry;
int CTheScripts::MainScriptSize;
int CTheScripts::LargestMissionScriptSize;
short CTheScripts::NumberOfMissionScripts;
short CTheScripts::NumberOfExclusiveMissionScripts;
int CTheScripts::LargestNumberOfMissionScriptLocalVariables;
int SaveGameStateType; //mobile only!
int totalActiveScripts;
CRunningScript scripts[totalActiveScripts];
-- PC save block ends here --
short resourcesCount;
tResourceManagerEntry resources[resourcesCount];
int IsOddJob;
if (IsOddJob != 1 && isMissionThread == 1){
  char missionText[8];
  int missionReplaySetting;
}

struct tResourceManagerEntry{
  WORD index; //index in manager array
  WORD scriptIndex; //index of a script
  int resourceID;
  WORD type;
  if (type == 2 && resourceID >= 290 && resourceID <= 299){
    int length;
    char modelName[length]
  }
}
Of course, I may be wrong.
  • OrionSR likes this

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#28

Posted 11 August 2017 - 08:46 PM

I wonder if I can replace a missionThread with something of my own, and execute custom cleo-like missions on iOS and Remastered versions.

 

Thanks again. I've got a busy work weekend so progress may slow for a bit. But I'll review what I've got and try to get things updated for another round.

 

In case you missed my added comment, please review my reply to your comments on the resource manager


Nick007J
  • Nick007J

    Mark Chump

  • Members
  • Joined: 17 Jan 2010
  • Russia

#29

Posted 11 August 2017 - 09:03 PM Edited by Nick007J, 11 August 2017 - 09:24 PM.

Do you mean different scructure than what I posted now? I may be checking too old (too new) version, maybe it was changed at some point? Because what I see is that game clearly writes length of a string, followed by string itself, and in that particular order. I will try to find different android versions to check.

Edit: Checked 1.01 and 1.08, in both special model block is saved as 4 bytes of length of string + the string itself. Weird.

Edit 2: I modified your template into following structure (sorry for bad names, just wanted to try):

    if (isMobile == 1) {
        WORD                MobileCount;    // nRecords

        struct tMobileStruct
        {
            struct tModel
            {
                WORD                MobileA;        // minIndex
                WORD                MobileB;        // maxIndex
                int                Mobile;           // modelID [290 to 299
                WORD               Mobile1;          // type?
                if (Mobile >= 290 && 299 >= Mobile) {
                    int t;
                    char mobileString[t]; //modelName
                }
            } Model[MobileCount] <optimize=false>;
        } MobileStruct;
    }
    int oddjob;
and I think successfully opened a savefile you provided earlier.

OrionSR
  • OrionSR

    Chain Game Development Team

  • Feroci
  • Joined: 23 May 2007
  • None
  • Helpfulness Award [GTA & Modding]

#30

Posted 11 August 2017 - 09:28 PM Edited by OrionSR, 11 August 2017 - 09:35 PM.

I am unaware of any changes to the save structure since v1.06. Anything before that is so cluttered with garbage bytes that revising the template to compensate is a hopeless proposition. 

 

It seems that we are out of sync with expectations and observations. I'm not even sure what structures were taking about now. 

 

On some occasions I find it useful to seed unique data into the save and observe where it ends up in memory. Or the other way around. 

 

Full Script Struct.

// Block 1: Script
struct tBlock01
{
    tblockHeader header;
    
    int                 varSpaceSize;
    int                 globalVar[varSpaceSize/4];
    tExternalScriptTrigger trigger[70];
    int                 onMissionFlagOffset;
    int                 lastMissionTime;

    struct tStaticReplacement
    {
        DWORD       type; /*always 2*/
        int         handle;
        int         newModelID, oldModelID;
    } staticReplacement[25];   

    struct tInvisibleObject
    {
        int   type, handle;
    } invisibleObject[20];
    int                 disabledCarparkingModels[20];

    struct tLodAssignment
    {
        int   objHandle, lodHandle;
    } LodAssignment[10];

    struct tScriptAssignment
    {
        int     actorModelID;
        char    scriptName[8];
        DWORD   unknown[2];
    
    } ScriptAssignment[8];
    byte                unknown1, unknown2;
    int                 scmMainSize;
    int                 largestMissionSize;
    int                 missionsNumber;
    int                 highestLocalVariable;
    int                 nRunningThreads;
    if (isMobile == 1)  int SaveGameStateType;    //mobile
    tThread             threads[nRunningThreads] <optimize=false>;
    if (isMobile == 1) {
        WORD                MobileCount;    // nRecords
        WORD                MobileA;        // minIndex
        WORD                MobileB;        // 
        struct tMobileStruct
        {
            struct tModel
            {
                int                modelID;           // modelID
                WORD               Mobile1;          // type?
                WORD               Mobile2;          // index? [string length]
                WORD               Mobile3;          // maxIndex?
                if (modelID >= 290 && 299 >= modelID) {
                    char modelName[Mobile2];
                    int MobileInt <format=hex>;              //handle?
                }
            } Model[MobileCount] <optimize=false>;
        } MobileStruct;
        if (isMissionThread == 1) {
//        if (isMissionThread == 1 && isOddJob == 0) {
            char missionText[8];
            int missionReplaySetting;
        }
    }
} Script;





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users